67

我会与很多我没有编写的深度嵌套的 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 的函数...

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

10 回答 10

47

您可以使用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']
于 2013-03-22T14:50:33.170 回答
45

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

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

但请注意,您不能只将赋值本身放在修饰函数中。您需要从修饰函数返回 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
于 2014-12-16T08:01:09.893 回答
27

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

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

于 2014-12-12T15:30:58.490 回答
10

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

如果您唯一的用例是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 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 函数提供的包装器会引发异常,然后将其静音。

于 2013-03-22T14:23:41.420 回答
9

在您的情况下,您首先评估 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) 
于 2013-03-22T14:27:03.630 回答
8

扩展@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')
于 2018-12-16T09:17:51.727 回答
4

由于您正在处理大量损坏的代码,因此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)""")
于 2014-12-16T01:57:23.303 回答
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 ''

如果你有几个地方需要获得价值,你也可以结合这两种解决方案,而辅助功能会更合理。

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

于 2013-03-22T14:32:04.717 回答
0

尝试使用除装饰器来实现同步和异步功能

注:logger.error可以换成print

最新版本可以在这里找到。

在此处输入图像描述

于 2021-03-01T22:28:35.083 回答
0

像这样的东西怎么样:

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

于 2021-12-14T07:21:18.913 回答