除了在python中包装try的一般装饰器?

时间:2013-03-22 14:04:36

标签: python try-catch wrapper decorator

我正在与很多深度嵌套的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)包装在某个函数中,该函数将执行暂停异常转换为空字段,打印到stdout。前者将是一种逐项跳过 - 其中该键不可用,它记录为空白并继续前进 - 后者是行跳过,如果任何字段不起作用,则整个记录为跳过。

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

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的函数...

  1. 当方法不存在时。
  2. 当一个对象不存在时,并且正在获取一个调用它的方法。
  3. 当一个不存在的对象被调用为函数的参数时。
  4. 任何这些东西的任何组合。
  5. 奖金,当一个功能不存在时。

9 个答案:

答案 0 :(得分:31)

您可以使用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']

答案 1 :(得分:19)

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

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

但请注意,您不能将赋值本身放在装饰函数中。你需要从装饰函数返回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:
         f(*args,**kwargs)
      except:
         print('Error')

    return applicator

  return decorate

答案 2 :(得分:16)

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

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我将错误的默认值更改为具有基本异常的元组以防止错误。

答案 3 :(得分:8)

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

如果您的唯一用例是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()会调用调用lambda的包装器,该lambda尝试调用my_function()。如果这不存在,则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函数,该函数引发异常然后被静音。

答案 4 :(得分:8)

在你的情况下,你首先评估喵叫(不存在)的值,然后将其包装在装饰器中。这不起作用。

首先在包装之前引发异常,然后包装器被错误地缩进(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) 

顺便说一句,从个人的角度来看,我也喜欢使用contextmanager的@cravoori解决方案。但这意味着每次都有三行代码:

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

答案 5 :(得分:5)

由于您处理了大量已损坏的代码,因此在这种情况下使用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)""")

答案 6 :(得分:2)

为什么不直接使用周期?

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

如果您有一些需要获得价值的地方,辅助功能会更合理,您也可以将两种解决方案结合起来。

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

答案 7 :(得分:2)

尝试除装饰器之外的同步和异步功能

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

最新版本可以在here找到。

enter image description here

答案 8 :(得分:0)

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