用于包装尝试的通用装饰器除了在 python 中?

Posted

技术标签:

【中文标题】用于包装尝试的通用装饰器除了在 python 中?【英文标题】:General decorator to wrap try except in python? 【发布时间】:2013-03-12 10:11:17 【问题描述】:

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

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

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

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'] = ''

这是我想要的,(1):

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

或(2):

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

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

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

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

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

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

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

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

更新

我在下面得到了很多非常有用、有帮助的答案,并感谢他们——但我已经编辑了上面使用的示例来说明我试图捕捉的不仅仅是嵌套的键错误,我正在寻找一个专门为...包装 try-catch 的函数。

    当方法不存在时。 当一个对象不存在,并且正在获取对其调用的方法时。 当一个不存在的对象作为函数的参数被调用时。 这些东西的任意组合。 奖励,当函数不存在时。

【问题讨论】:

对于专门访问嵌套 JSON,您可能需要查看 safeJSON。这是通过有效地包装对象myobject来实现的。 【参考方案1】:

您可以使用 defaultdict 和 the context manager approach as outlined in Raymond Hettinger's PyCon 2013 presentation

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']

【讨论】:

嗯,这很好。我要对此进行调查。谢谢。 链接已关闭。任何想法在哪里可以找到它? 那是一个很棒的演讲,真的为我带来了很多东西。我现在将它与Code Like a Pythonista: Idiomatic Python 一起推荐。谢谢。 ignored 函数中而不是使用 pass 我可以使用 raise 并在主调用中捕获异常以打印相关代码块的错误消息吗? @iruvar @alper,这会破坏使用ignored 的意义,不是吗。如果我理解您的问题,您寻求打印的详细信息与手头的例外情况相对应。你可以这样做 - import sys, traceback 然后在 ignored 中的 pass 之前添加 ex_type, ex, tb = sys.exc_info() 后跟 traceback.print_tb(tb) 【参考方案2】:

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

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

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

根据您的示例代码,可以使用以下模式编写代码:

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

item["a"] = computeA()

return_on_failure 可能类似于:

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

    return applicator

  return decorate

【讨论】:

您的代码永远不会引用传递给装饰器的value 参数,因此装饰器函数将永远不会返回它。 IMO 这没有任何意义,也不会按照您的要求行事。 @MattV:我认为给这个答案加分是考虑不周的。【参考方案3】:

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

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)

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

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

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

【讨论】:

请见谅这里知识欠缺。我试图在我的脚本中使用它,但它并没有给我我正在寻找的结果。我的错误是由 f(soup.find("span", class_='xxx').text) 产生的 'AttributeError: 'NoneType' object has no attribute text'。我将装饰器定义为'f = get_decorator(errors=(AttributeError,), default_value="#NA")'。我在这里做错了什么? @MVersteeg:您需要将@f 应用于返回产生异常的soup.find("span", class_='xxx').text 表达式的值的函数——如答案中的示例所示。 赞成,尽管我会将get_decorator() 的签名更改为def get_decorator(default_value='', *errors) 以简化调用它。 @martineau 我更改了错误默认值以防止异常 值得注意的是——尤其是一次性使用——函数之前的@get_decorator((KeyError, NameError), default_value='default')也可以工作。【参考方案4】:

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

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

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

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

我会试着给你看:

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

尽管如此,f(some_undefined_function()) 不起作用,因为

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

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

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

wrapped_f = f(lambda: my_function())

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

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

如果您尝试将 lambda 函数替换为像这样的包装器,那么这种向 lambda 函数内部的移动是行不通的

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

后跟一个

f(g(my_function))(arguments)

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

如果你尝试做类似的事情

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

如果你没有x,它也不能正常工作,因为g() 保护print,而不是x

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

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

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

【讨论】:

这很有帮助,但我仍在努力。 (例外在上面拼错了,但是明白了。)再进一步,有没有一种方法可以代替使用 lambda 包装,而是创建另一个函数 g(),它在通用函数上为我执行 lambda 包装,包括 *args 和 **kwargs?这样我就可以调用 g(anyfunction()) 并且它会产生我正在寻找的全部效果? 当我尝试它时,我做了&gt;&gt;&gt; wrapped_p = f(lambda *z,**z: func(*z,**z))并得到了File "&lt;stdin&gt;", line 1 SyntaxError: duplicate argument 'z' in function definition 当然;你可以做一个g = lambda function: lambda *a, **k: function(*a, **k),它可以让你做g(anyfunction)()。 (只有这样:你不想包装函数结果,而是函数本身。) 谢谢,我太傻了。但是即使使用这个函数来包装我的函数,我似乎也有同样的问题。所以,&gt;&gt;&gt; g = f(lambda function: lambda *a, **k: function(*a,**k)) 然后我用&gt;&gt;&gt; g(print)(x.get('fail')) 调用它并得到一条消息 x 不存在,而不是我正在寻找的静默失败。 唷,当然。那是我的错。在这种情况下,由于名称解析发生的时间,它无法工作。它必须从立即执行中移开。我会在上面的回答中详细说明。【参考方案5】:

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

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

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

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

softfail =f(hardfail)

输出:

>>> 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

无论如何,在您的情况下,我不明白您为什么不使用诸如

之类的简单方法
def get_subkey(obj, key, subkey):
  try:
    return obj.get(key).get(subkey, '')
  except AttributeError:
    return ''

在代码中:

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

已编辑:

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

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

你会这样称呼:

>>> 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

并使用您的代码

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

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

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

【讨论】:

谢谢!在错误的地方返回修复了我认为我正在做的事情,但事实并非如此。但是我仍然不知道在换行之前如何调用 meow() 的内部异常。我不能使用简单方法的原因是我在几个不同的深度调用,使用了几个具有不同属性的不同对象。我会为每个作业编写一个函数,这和尝试捕捉一样麻烦。所以,我正在寻找可以捕获失败函数并返回“”的东西,将错误打印到标准输出。【参考方案6】:

扩展@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')

【讨论】:

【参考方案7】:

由于您要处理大量损坏的代码,在这种情况下使用eval 可能是可以原谅的。

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

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

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

【讨论】:

永远不要在任何编程语言中使用 eval【参考方案8】:

为什么不直接使用循环?

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] = ''

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

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

如果你有几个地方需要获取价值,你也可以将这两种解决方案结合起来,辅助函数会更合理。

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

【讨论】:

我正在尝试使其更通用,不仅仅是为了一级深度缺失的键,还有很多功能可能由于很多原因而无法分配数据。【参考方案9】:

这样的事情怎么样:

def exception_handler(func):
def inner_function(*args, **kwargs):
    try:
        func(*args, **kwargs)
    except TypeError:
        print(f"func.__name__ error")
return inner_function

然后

@exception_handler
def doSomethingExceptional():
    a=2/0

所有学分转到:https://medium.com/swlh/handling-exceptions-in-python-a-cleaner-way-using-decorators-fae22aa0abec

【讨论】:

【参考方案10】:

为同步和异步功能尝试除装饰器

注意:logger.error 可以替换为print

最新版本可以在here找到。

【讨论】:

请不要用图片代替代码。

以上是关于用于包装尝试的通用装饰器除了在 python 中?的主要内容,如果未能解决你的问题,请参考以下文章

python包装函数在装饰器中接受参数

我可以在包装函数之前修补 Python 装饰器吗?

python装饰函数调用

使用额外参数创建通用视图装饰器

为啥 Python 的装饰器语法比普通的包装器语法提供更快的记忆代码?

Python装饰器通用样式