【问题标题】:How to catch an exception in a decorator如何在装饰器中捕获异常
【发布时间】:2013-08-22 00:42:59
【问题描述】:

我有一个函数我的原因异常,我希望它成为一个装饰器。代码如下:

def des(i):
    def new_func(func):
        if i == 1:
            raise Exception
        else:
            return func
    return new_func


@des(1)
def func():
    print "!!"


if __name__ == '__main__':
    try:
        func()
    except Exception:
        print 'error'

但输出是:

Traceback (most recent call last):
  File "D:/des.py", line 10, in <module>
    @des(1)
  File "D:/des.py", line 4, in new_func
    raise Exception
Exception

那么,我怎样才能捕捉到这个异常呢?

【问题讨论】:

标签: python python-decorators


【解决方案1】:

当您定义函数时会引发异常。捕获此异常的唯一方法是:

try:
    @des(1)
    def func():
        print '!!'
except:
    print 'error'

如果对导致异常的原因感到困惑,请记住您的代码相当于:

def func():
    print '!!'
func = des(1)(func)
# des(1) = new_func, so des(1)(func) is new_func(func)

【讨论】:

  • 但它导致 func 是一个未定义的函数
  • 是的,如果在定义函数的过程中出现异常,则不会定义该函数。除了在except 语句中为func 分配不同的函数之外,真的没有办法解决这个问题。如果我知道实际代码是什么,而不是这个例子,我可以提供更多帮助。
  • @LiGa 是的,但是您可以通过异常检测到这一点并进行适当的处​​理。
【解决方案2】:

所以,现在你的代码基本上可以归结为:

_des = des(1)

def _func();
    print '!!'

func = _des(func)

您使用des 的返回值作为装饰器,我认为这是导致问题的原因。

我想你可能想再嵌套一次返回的函数:

def des(i): # container func.
    def new_func(func):
        def ret_func(*args, **kwargs):
            if i == 1:
                raise Exception
            else:
                return func(*args, **kwargs)

        return ret_func # return the func with the bound variable
    return new_func # return the func which creates the function w/ the bound var.


@des(1)
def func():
    print "!!"

【讨论】:

  • 我认为您希望最里面的函数调用func,而不仅仅是返回它。即,将return func 替换为return func()。您可能希望使用可变参数语法 (*args, **kwargs) 来允许修饰函数也接受一些参数。
  • @Blckknght 但是缺少一层功能。看我的回答。
  • @glglgl:我不确定我是否遵循。 cwallenpool 的答案具有正确数量的嵌套函数,尽管没有像您的那样整齐地呈现(并且没有 () 使最里面的函数工作)。您实际上可以在 i==1 上创建最内层函数,如果不需要,只需从装饰器(第二级函数)返回 func
  • @Blckknght 是的,但是函数的装饰再次失败而不是调用。
  • @Blckknght 天哪!好吧,现在修好了。
【解决方案3】:

我在这里缺少一个功能级别。

ITYM

import functools
def des(i): # this is the "decorator creator", called with des(1)
    def deco(func): # this is returned and is the real decorator, called at function definition time
        @functools.wraps(func) # sugar
        def new_func(*a, **k): # and this is the function called on execution.
            if i == 1:
                raise Exception # I hope this is just for testing... better create a new exception for this
            else:
                return func(*a, **k)
        return new_func
    return deco

@des(1)
def func():
    print "!!"

if __name__ == '__main__':
    try:
        func()
    except Exception:
        print 'error'

【讨论】:

    【解决方案4】:

    正如其他答案所解释的,您当前的问题是当装饰器应用于函数时引发异常,而不是在调用函数时引发异常。

    要解决这个问题,您需要让装饰器返回一个引发异常的函数。以下是它的工作原理:

    import functools
    
    def des(i):
        def decorator(func):
            if i != 1:
                return func # no wrapper needed
    
            @functools.wraps(func)
            def raiser(*args, **kwargs):
                raise Exception
    
            return raiser
    
        return decorator
    

    des 函数是一个“装饰器工厂”。除了提供一个范围来保存它返回的装饰器的 i 参数之外,它实际上并没有做任何事情。

    decorator 函数会检查是否需要执行任何特殊操作。如果不是,则返回未修改的修饰函数。如果i==1,则返回一个自定义函数。

    raiser 函数是装饰器在i==1 时的返回值。它总是在被调用时引发异常。应用于它的functools.wraps 装饰器不是绝对必要的,但它使它看起来更像原始函数(相同的__name____doc__ 等)。

    【讨论】:

    • 这也是一个不错的解决方案:i == 1!= 之间的区别尽可能早地发生,但异常发生在调用时,就像最喜欢的那样。
    猜你喜欢
    • 2019-10-05
    • 2013-12-31
    • 2016-06-16
    • 2019-06-23
    • 2011-05-14
    • 2016-05-12
    • 1970-01-01
    • 2014-11-30
    • 1970-01-01
    相关资源
    最近更新 更多