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

Mit*_*ops 41 python try-catch decorator wrapper

我正在与许多深度嵌套的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'] = ''
Run Code Online (Sandbox Code Playgroud)

这是我想要的,(1):

item['a'] = f(myobject.get('key').get('subkey'))
item['b'] = f(myobject.get('key2'))
item['c'] = f(func1(myobject)
...
Run Code Online (Sandbox Code Playgroud)

或(2):

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

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

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

def f(func):
   def silenceit():
      try:
         func(*args,**kwargs)
      except:
         print('Error')
      return(silenceit)
Run Code Online (Sandbox Code Playgroud)

这就是为什么它不起作用.调用一个不存在的函数,它不会尝试捕获它:

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

在我添加空白返回值之前,我想让它正确地尝试捕获.如果该功能有效,这将打印"错误",对吧?

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

UPDATE

我在下面有很多非常有用的,有用的答案,谢谢你们 - 但我编辑了上面用过的例子来说明我试图捕获的不仅仅是嵌套的键错误,我是我专门寻找一个包含try-catch的函数...

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

iru*_*var 34

你可以使用一个defaultdict和上下文管理器方法,如Raymond Hettinger的PyCon 2013演示文稿中所述

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']
Run Code Online (Sandbox Code Playgroud)

  • @alper,这会破坏使用“ignored”的意义,不是吗?如果我理解你的问题,你会寻求打印与当前异常相对应的详细信息。你可以这样做 - `import sys, traceback` 然后在 `ignored` 中的 `pass` 之前添加 `ex_type, ex, tb = sys.exc_info()`,后跟 `traceback.print_tb(tb)` (3认同)

Nat*_*vis 22

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

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

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

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

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

item["a"] = computeA()
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


Ser*_*rew 20

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

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)
Run Code Online (Sandbox Code Playgroud)

只需传递给get_decorator元组,其中包含您想要静默的错误类型以及要返回的默认值.输出将是

Got error!  KeyError('b',)
default
Got error!  NameError("global name 'doesnt_exist' is not defined",)
default
Run Code Online (Sandbox Code Playgroud)

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


glg*_*lgl 8

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

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

item['b'] = myobject.get('key2', '')
Run Code Online (Sandbox Code Playgroud)

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

我会试着告诉你:

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
Run Code Online (Sandbox Code Playgroud)

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

a)f()在执行时间尚未生效

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

一层"lambda"在这里会有所帮助:

wrapped_f = f(lambda: my_function())
Run Code Online (Sandbox Code Playgroud)

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

这是有效的,因为名称my_function不是在定义lambda时执行,而是在执行时执行.然后,该执行受到函数的保护和包装f().因此异常发生在lambda内部并传播到装饰器提供的包装函数,装饰器正常处理它.

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

g = lambda function: lambda *a, **k: function(*a, **k)
Run Code Online (Sandbox Code Playgroud)

接下来是

f(g(my_function))(arguments)
Run Code Online (Sandbox Code Playgroud)

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

如果你尝试做类似的事情

g(print)(x.get('fail'))
Run Code Online (Sandbox Code Playgroud)

如果你没有x,它就无法运作,因为g()保护print,不是x.

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

value = f(lambda: x.get('fail'))
Run Code Online (Sandbox Code Playgroud)

因为f()lambda函数调用提供的包装器会引发异常然后被静音.


ast*_*eal 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)
Run Code Online (Sandbox Code Playgroud)

输出:

>>> 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
Run Code Online (Sandbox Code Playgroud)

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

def get_subkey(obj, key, subkey):
  try:
    return obj.get(key).get(subkey, '')
  except AttributeError:
    return ''
Run Code Online (Sandbox Code Playgroud)

并在代码中:

 item['a'] = get_subkey(myobject, 'key', 'subkey')
Run Code Online (Sandbox Code Playgroud)

编辑:

如果你想要一些可以在任何深度工作的东西.你可以这样做:

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

那你打电话:

>>> 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}
Run Code Online (Sandbox Code Playgroud)

并使用您的代码

item['a'] = get_from_object(obj, 2, 3) 
Run Code Online (Sandbox Code Playgroud)

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

item['a'] = ''
with ignored(AttributeError):
  item['a'] = obj.get(2).get(3) 
Run Code Online (Sandbox Code Playgroud)


Pax*_*x0r 7

扩展@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')
Run Code Online (Sandbox Code Playgroud)


Add*_*son 5

由于您处理的是大量已损坏的代码,因此eval在这种情况下使用它可能是不可原谅的.

def my_eval(code):
  try:
    return eval(code)
  except:  # Can catch more specific exceptions here.
    return ''
Run Code Online (Sandbox Code Playgroud)

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

item['a'] = my_eval("""myobject.get('key').get('subkey')""")
item['b'] = my_eval("""myobject.get('key2')""")
item['c'] = my_eval("""func1(myobject)""")
Run Code Online (Sandbox Code Playgroud)

  • 从未在任何编程语言中使用过eval (2认同)