Python装饰的异常不能被捕获。

3
我用装饰器类来装饰一个异常,但是似乎这个异常不能被精确地捕获到。使用 functools.update_wrapper 来更新装饰器类也不起作用。
class ClsDecor(object):

    def __init__(self,cls):
        self.cls=cls
        self.counter=0

    def __call__(self,*args):
        self.counter+=1
        return self.cls(*args)

@ClsDecor
class Err(Exception):
    def __init__(self):
      Exception.__init__(self)

try:
    raise Err()
except Err as e:
    print 'catched'
except Exception as e:
    print 'Not catched'

你尝试过从Exception派生装饰器吗? - Ignacio Vazquez-Abrams
谢谢,尝试过了,不起作用。 - Max
2
@ClsDecor 之后,ErrClsDecor 的一个 实例Err() 返回最初的 Err 类的一个实例(其名称被装饰覆盖)。现在,在 Err 和从 Err() 返回的实例之间存在明显的类型不匹配。实际上类型是 Err.cls - dhke
谢谢,是的,Err.cls可以解决问题,有没有什么包装工具来解决这个问题? - Max
在默认的异常情况下,当我捕获到异常时,它的类型是<class'__main__.Err'>。 - Max
3个回答

2

首先,方法__call__应该是:


def __call__(self, *args):
    return self.cls(*args)

你的代码将在__call__函数中引发错误,直接跳转到print 'Not catched',这是第一级错误。修复此错误后,您将达到第二级错误的类型不匹配,dhke在注释中对此进行了很好的解释。基本思路是,@ClsDecor之后,Err实际上是ClsDecor类型,但是你在__call__中返回一个原始类型Err实例,显然与except Err不匹配。 您可以轻松使用装饰器来实现您的目的,例如:

def err_dec(Cls):
    class NewErr(Exception):
        def __init__(self, *args, **kwargs):
            self.err = Cls(*args, **kwargs)
return NewErr

@err_dec
class Err(Exception):
    def __init__(self):
        Exception.__init__(self)

根据评论中的要求进行更新:

def err_dec(Cls):
    class NewErr(Exception):
        c = 0
        def __init__(self, *args, **kwargs):
            NewErr.c = NewErr.c + 1
            self.err = Cls(*args, **kwargs)
        def counter(self):
            return NewErr.c
    return NewErr

@err_dec
class Err(Exception):
    def __init__(self):
        Exception.__init__(self)

try:
    Err()
    Err()
    raise Err()
except Err as e:
    print e.counter()
    print 'catched'
except Exception as e:
    print 'Not catched'

谢谢,我的初衷是为某些异常制作一个计数器装饰器,这样在处理后,我就可以得到遇到了多少个这种可忽略错误的计数器。 - Max
你可以查看我回答中更新的部分 :-) 希望能对你有所帮助。 - shizhz
谢谢,你帮了很多忙。最后我使用元类来修改新类名,以使异常匹配原始名称。^_^ - Max

0

问题不在于装饰。也许装饰让你感到困惑,但那不是问题所在。

你需要意识到 Err 是什么。装饰器的工作方式类似于普通定义,但是在将对象分配给名称之前,您需要将装饰器应用于该对象。这相当于:

class Err(Exception):
def __init__(self):
  Exception.__init__(self)
Err = ClsDecor(Err)

现在Err是一个ClsDecor的实例,当抛出它时,你调用的是实例(而不是类),你将调用ClsDecor.__call__(而不是ClsDecor.__init__)。问题出在这里,因为你试图用一个参数(args)去调用self.cls,但是self.clsErr,它不接受任何额外的参数(除了self)。
如果在默认的except子句中打印错误信息,你就会看到这个错误。如果遇到默认子句,打印错误信息是一个好主意,因为通常是因为捕获到了意外的异常。
except Exception as e:
    print 'Not catched', repr(e)
    raise

或许你根本不应该捕获它 - 如果没有被捕获,异常和回溯信息仍然会被打印出来。


我试图在默认异常处理中获取类型信息,结果是<class'__main__.Err'>,让我很困惑,因为我认为它应该被视为类型Err。 - Max
@user3011780 这不应该发生,当我尝试时会出现 TypeError。另一件你应该考虑的事情是根本不要有默认的 except 子句,通常这意味着你会得到一个你没有预料到的异常,而且你也不知道如何处理它,然后你可以让它传播并导致 Python 的正常堆栈跟踪错误消息。这将显示出错误发生的位置。 - skyking
TypeError应该是由我之前的帖子中'*args'写成了'args'导致的拼写错误。非常感谢您的建议。 - Max

0

在大家的帮助下,我终于得到了这样的解决方案:

    class ErrMetaClass(type):
        def __new__(mcls,cls_name,cls_parents,cls_attr):
            return super(ErrMetaClass,mcls).__new__(mcls, 
                   cls_attr['_cls'].__name__, cls_parents,cls_attr)

    def err_dec(cls):
        class NewErr(Exception):
            __metaclass__ = ErrMetaClass
            _cls=cls
            c=0
            def __init__(self):
                NewErr.c+=1
        return NewErr

    @err_dec
    class Err(Exception):
        def __init__(self):
          Exception.__init__(self)

    try:
        raise Err()
    except Err as e:
        print Err.c
    except Exception as e:
        print type(e)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接