通用装饰器来包装尝试,除了在 python 中?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15572288/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 20:26:49  来源:igfitidea点击:

General decorator to wrap try except in python?

pythontry-catchwrapperdecorator

提问by Mittenchops

I'd interacting with a lot of deeply nested json I didn't write, and would like to make my python script more 'forgiving' to invalid input. I find myself writing involved try-except blocks, and would rather just wrap the dubious function up.

我会与许多我没有编写的深度嵌套的 json 交互,并且想让我的 python 脚本对无效输入更加“宽容”。我发现自己编写涉及 try-except 块,而宁愿只是将可疑函数包装起来。

I understand it's a bad policy to swallow exceptions, but I'd rather prefer they to be printed and analysed later, than to actually stop execution. It's more valuable, in my use-case to continue executing over the loop than to get all keys.

我知道吞下异常是一个糟糕的策略,但我宁愿稍后打印和分析它们,而不是实际停止执行。在我的用例中,继续在循环中执行比获取所有键更有价值。

Here's what I'm doing now:

这是我现在正在做的事情:

try:
    item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST()
except:
    item['a'] = ''
try:
    item['b'] = OBJECT_THAT_DOESNT_EXIST.get('key2')
except:
    item['b'] = ''
try:
    item['c'] = func1(ARGUMENT_THAT_DOESNT_EXIST)
except:
    item['c'] = ''
...
try:
    item['z'] = FUNCTION_THAT_DOESNT_EXIST(myobject.method())
except:
    item['z'] = ''

Here's what I'd like, (1):

这是我想要的,(1):

item['a'] = f(myobject.get('key').get('subkey'))
item['b'] = f(myobject.get('key2'))
item['c'] = f(func1(myobject)
...

or (2):

或(2):

@f
def get_stuff():
   item={}
   item['a'] = myobject.get('key').get('subkey')
   item['b'] = myobject.get('key2')
   item['c'] = func1(myobject)
   ...
   return(item)

...where I can wrap either the single data item (1), or a master function (2), in some function that turns execution-halting exceptions into empty fields, printed to stdout. The former would be sort of an item-wise skip - where that key isn't available, it logs blank and moves on - the latter is a row-skip, where if any of the fields don't work, the entire record is skipped.

...我可以将单个数据项 (1) 或主函数 (2) 包装在某个函数中,该函数将执行暂停异常转换为空字段,打印到标准输出。前者将是一种逐项跳过 - 在该键不可用的情况下,它记录空白并继续前进 - 后者是一个行跳过,如果任何字段不起作用,则整个记录是跳过。

My understanding is that some kind of wrapper should be able to fix this. Here's what I tried, with a wrapper:

我的理解是某种包装器应该能够解决这个问题。这是我尝试过的,带有包装器:

def f(func):
   def silenceit():
      try:
         func(*args,**kwargs)
      except:
         print('Error')
      return(silenceit)

Here's why it doesn't work. Call a function that doesn't exist, it doesn't try-catch it away:

这就是它不起作用的原因。调用一个不存在的函数,它不会尝试抓住它:

>>> f(meow())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'meow' is not defined

Before I even add a blank return value, I'd like to get it to try-catch correctly. If the function had worked, this would have printed "Error", right?

在我什至添加一个空白的返回值之前,我想让它正确地尝试捕获。如果该功能有效,这将打印“错误”,对吗?

Is a wrapper function the correct approach here?

包装函数在这里是正确的方法吗?

UPDATE

更新

I've had a lot of really useful, helpful answers below, and thank you for them---but I've edited the examples I used above to illustrate that I'm trying to catch more than nested key errors, that I'm looking specifically for a function that wraps a try-catch for...

我在下面有很多非常有用、有用的答案,感谢您提供的答案——但我已经编辑了上面使用的示例来说明我试图捕获的不仅仅是嵌套的关键错误,我我专门寻找一个函数来包装一个 try-catch ......

  1. When a method doesn't exist.
  2. When an object doesn't exist, and is getting a method called on it.
  3. When an object that does not exist is being called as an argument to a function.
  4. Any combination of any of these things.
  5. Bonus, when a function doesn't exist.
  1. 当方法不存在时。
  2. 当一个对象不存在,并且正在调用一个方法时。
  3. 当一个不存在的对象作为函数的参数被调用时。
  4. 任何这些东西的任何组合。
  5. 奖励,当一个函数不存在时。

回答by glglgl

It depends on what exceptions you expect.

这取决于您期望的例外情况。

If your only use case is get(), you could do

如果你唯一的用例是get(),你可以做

item['b'] = myobject.get('key2', '')

For the other cases, your decorator approach might be useful, but not in the way you do it.

对于其他情况,您的装饰器方法可能很有用,但不是您使用的方式。

I'll try to show you:

我会试着向你展示:

def f(func):
   def silenceit(*args, **kwargs): # takes all kinds of arguments
      try:
         return func(*args, **kwargs) # returns func's result
      except Exeption, e:
         print('Error:', e)
         return e # not the best way, maybe we'd better return None
                  # or a wrapper object containing e.
  return silenceit # on the correct level

Nevertheless, f(some_undefined_function())won't work, because

然而,f(some_undefined_function())不会工作,因为

a) f()isn't yet active at the executon time and

a)f()在执行时尚未激活,并且

b) it is used wrong. The right way would be to wrap the function and then call it: f(function_to_wrap)().

b) 使用错误。正确的方法是来包装函数,然后调用它:f(function_to_wrap)()

A "layer of lambda" would help here:

“Lambda 层”在这里会有所帮助:

wrapped_f = f(lambda: my_function())

wraps a lambda function which in turn calls a non-existing function. Calling wrapped_f()leads to calling the wrapper which calls the lambda which tries to call my_function(). If this doesn't exist, the lambda raises an exception which is caught by the wrapper.

包装一个 lambda 函数,该函数又调用一个不存在的函数。调用wrapped_f()导致调用调用 lambda 的包装器,该 lambda 试图调用my_function(). 如果这不存在,则 lambda 会引发由包装器捕获的异常。

This works because the name my_functionis not executed at the time the lambda is defined, but when it is executed. And this execution is protected and wrapped by the function f()then. So the exception occurs inside the lambda and is propagated to the wrapping function provided by the decorator, which handles it gracefully.

这是有效的,因为名称my_function不是在定义 lambda 时执行,而是在执行时执行。然后这个执行被函数保护和包装f()。所以异常发生在 lambda 内部,并传播到装饰器提供的包装函数,它优雅地处理它。

This move towards inside the lambda function doesn't work if you try to replace the lambda function with a wrapper like

如果您尝试用类似的包装器替换 lambda 函数,则这种向 lambda 函数内部移动的操作不起作用

g = lambda function: lambda *a, **k: function(*a, **k)

followed by a

接着是一个

f(g(my_function))(arguments)

because here the name resolution is "back at the surface": my_functioncannot be resolved and this happens before g()or even f()are called. So it doesn't work.

因为这里的名称解析是“回到表面”:my_function无法解析,这发生在g()甚至f()被调用之前。所以它不起作用。

And if you try to do something like

如果你尝试做类似的事情

g(print)(x.get('fail'))

it cannot work as well if you have no x, because g()protects print, not x.

如果您没有x,它就无法正常工作,因为g()保护print,而不是x

If you want to protect xhere, you'll have to do

如果你想保护x这里,你必须这样做

value = f(lambda: x.get('fail'))

because the wrapper provided by f()calls that lambda function which raises an exception which is then silenced.

因为由f()调用该 lambda 函数提供的包装器引发了一个异常,然后被静音。

回答by astreal

in your case you first evaluate the value of the meow call (which doesn't exist) and then wrap it in the decorator. this doesn't work that way.

在您的情况下,您首先评估 meow 调用的值(不存在),然后将其包装在装饰器中。这不是那样工作的。

first the exception is raised before it was wrapped, then the wrapper is wrongly indented (silenceitshould not return itself). You might want to do something like:

首先在包装之前引发异常,然后包装器缩进错误(silenceit不应返回自身)。您可能想要执行以下操作:

def hardfail():
  return meow() # meow doesn't exist

def f(func):
  def wrapper():
    try:
      func()
    except:
      print 'error'
  return wrapper

softfail =f(hardfail)

output:

输出:

>>> softfail()
error

>>> hardfail()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in hardfail
NameError: global name 'meow' is not defined

anyway in your case I don't understand why you don't use a simple method such as

无论如何,在您的情况下,我不明白您为什么不使用简单的方法,例如

def get_subkey(obj, key, subkey):
  try:
    return obj.get(key).get(subkey, '')
  except AttributeError:
    return ''

and in the code:

并在代码中:

 item['a'] = get_subkey(myobject, 'key', 'subkey')

Edited:

编辑:

In case you want something that will work at any depth. You can do something like:

如果你想要一些可以在任何深度工作的东西。您可以执行以下操作:

def get_from_object(obj, *keys):
  try:
    value = obj
    for k in keys:
        value = value.get(k)
    return value
  except AttributeError:
    return ''

That you'd call:

你会打电话给:

>>> d = {1:{2:{3:{4:5}}}}
>>> get_from_object(d, 1, 2, 3, 4)
5
>>> get_from_object(d, 1, 2, 7)
''
>>> get_from_object(d, 1, 2, 3, 4, 5, 6, 7)
''
>>> get_from_object(d, 1, 2, 3)
{4: 5}

And using your code

并使用您的代码

item['a'] = get_from_object(obj, 2, 3) 

By the way, on a personal point of view I also like @cravoori solution using contextmanager. But this would mean having three lines of code each time:

顺便说一句,就个人观点而言,我也喜欢使用 contextmanager 的 @cravoori 解决方案。但这意味着每次都需要三行代码:

item['a'] = ''
with ignored(AttributeError):
  item['a'] = obj.get(2).get(3) 

回答by simplylizz

Why not just use cycle?

为什么不直接使用循环?

for dst_key, src_key in (('a', 'key'), ('b', 'key2')):
    try:
        item[dst_key] = myobject.get(src_key).get('subkey')
    except Exception:  # or KeyError?
        item[dst_key] = ''

Or if you wish write a little helper:

或者如果你想写一个小帮手:

def get_value(obj, key):
    try:
        return obj.get(key).get('subkey')
    except Exception:
        return ''

Also you can combine both solutions if you have a few places where you need to get value and helper function would be more reasonable.

如果您有几个需要获取价值的地方,并且辅助功能会更合理,您也可以将这两种解决方案结合起来。

Not sure that you actually need a decorator for your problem.

不确定您是否真的需要一个装饰器来解决您的问题。

回答by iruvar

You could use a defaultdict and the context manager approach as outlined in Raymond Hettinger's PyCon 2013 presentation

您可以使用Raymond Hettinger 的 PyCon 2013 演示中概述的 defaultdict 和上下文管理器方法

from collections import defaultdict
from contextlib import contextmanager

@contextmanager
def ignored(*exceptions):
  try:
    yield
  except exceptions:
    pass 

item = defaultdict(str)

obj = dict()
with ignored(Exception):
  item['a'] = obj.get(2).get(3) 

print item['a']

obj[2] = dict()
obj[2][3] = 4

with ignored(Exception):
  item['a'] = obj.get(2).get(3) 

print item['a']

回答by Sergeev Andrew

It's very easy to achieve using configurable decorator.

使用可配置的装饰器很容易实现。

def get_decorator(errors=(Exception, ), default_value=''):

    def decorator(func):

        def new_func(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except errors, e:
                print "Got error! ", repr(e)
                return default_value

        return new_func

    return decorator

f = get_decorator((KeyError, NameError), default_value='default')
a = {}

@f
def example1(a):
    return a['b']

@f
def example2(a):
    return doesnt_exist()

print example1(a)
print example2(a)

Just pass to get_decorator tuples with error types which you want to silence and default value to return. Output will be

只需将错误类型传递给 get_decorator 元组,您要使其静音并返回默认值。输出将是

Got error!  KeyError('b',)
default
Got error!  NameError("global name 'doesnt_exist' is not defined",)
default

Edit:Thanks to martineau i changed default value of errors to tuples with basic Exception to prevents errors.

编辑:多亏了martineau,我将错误的默认值更改为具有基本异常的元组以防止错误。

回答by Addison

Since you're dealing with lots of broken code, it may be excusable to use evalin this case.

由于您正在处理大量损坏的代码,因此eval在这种情况下使用可能是情有可原的。

def my_eval(code):
  try:
    return eval(code)
  except:  # Can catch more specific exceptions here.
    return ''

Then wrap all your potentially broken statements:

然后包装所有可能损坏的语句:

item['a'] = my_eval("""myobject.get('key').get('subkey')""")
item['b'] = my_eval("""myobject.get('key2')""")
item['c'] = my_eval("""func1(myobject)""")

回答by Nathan Davis

There are lots of good answers here, but I didn't see any that address the question of whether you can accomplish this via decorators.

这里有很多很好的答案,但我没有看到任何解决您是否可以通过装饰器完成此问题的答案。

The short answer is "no," at least not without structural changes to your code. Decorators operate at the function level, not on individual statements. Therefore, in order to use decorators, you would need to move each of the statements to be decorated into its own function.

简短的回答是“不”,至少在没有对代码进行结构性更改的情况下不会。装饰器在函数级别运行,而不是在单个语句上运行。因此,为了使用装饰器,您需要将要装饰的每个语句移动到其自己的函数中。

But note that you can't just put the assignment itself inside the decorated function. You need to return the rhs expression (the value to be assigned) from the decorated function, then do the assignment outside.

但请注意,您不能将赋值本身放在装饰函数中。你需要从装饰函数返回rhs表达式(要赋值的值),然后在外面进行赋值。

To put this in terms of your example code, one might write code with the following pattern:

就您的示例代码而言,您可能会使用以下模式编写代码:

@return_on_failure('')
def computeA():
    item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST()

item["a"] = computeA()

return_on_failurecould be something like:

return_on_failure可能是这样的:

def return_on_failure(value):
  def decorate(f):
    def applicator(*args, **kwargs):
      try:
         f(*args,**kwargs)
      except:
         print('Error')

    return applicator

  return decorate

回答by Pax0r

Extending @iruvar answer - starting with Python 3.4 there is an existing context manager for this in Python standard lib: https://docs.python.org/3/library/contextlib.html#contextlib.suppress

扩展@iruvar 答案 - 从 Python 3.4 开始,Python 标准库中有一个现有的上下文管理器:https://docs.python.org/3/library/contextlib.html#contextlib.suppress

from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

with suppress(FileNotFoundError):
    os.remove('someotherfile.tmp')