将super方法与类装饰器一起用于派生类时的TypeError

San*_*raj 2 python decorator python-decorators

首先,为长期解释道歉.

版本#1 - 代码:类的类装饰器

class A(object):
    def __init__(self, klass):
        print "A::__init__()"
        self._klass = klass

    def __call__(self):
        print "A::__call__()"
        return self._klass()

    def __del__(self):
        print "A::__del__()"

@A
class B(object):
    def __init__(self):
        print "B::__init__()"

def main():
    b = B()

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

版本#1 - 输出:

A::__init__()
A::__call__()
B::__init__()
A::__del__()
Run Code Online (Sandbox Code Playgroud)

版本#2 - 代码:派生类的类装饰器,它显式初始化基类.

class A(object):
    def __init__(self, klass):
        print "A::__init__()"
        self._klass = klass

    def __call__(self):
        print "A::__call__()"
        return self._klass()

    def __del__(self):
        print "A::__del__()"

class Parent1(object):
    def __init__(self):
        print "Parent1:: __init__()"
        super(Parent1, self).__init__()

class Parent2(object):
    def __init__(self):
        print "Parent2:: __init__()"
        super(Parent2, self).__init__()    

@A
class B(Parent1, Parent2):
    def __init__(self):
        print "B::__init__()"
#        super(B, self).__init__()
        Parent1.__init__(self)
        Parent2.__init__(self)

def main():
    b = B()

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

版本#2 - 输出:

A::__init__()
A::__call__()
B::__init__()
Parent1:: __init__()
Parent2:: __init__()
Parent2:: __init__()
A::__del__()
Run Code Online (Sandbox Code Playgroud)

版本#3 - 代码:派生类的类装饰器 super()

class A(object):
    def __init__(self, klass):
        print "A::__init__()"
        self._klass = klass

    def __call__(self):
        print "A::__call__()"
        return self._klass()

    def __del__(self):
        print "A::__del__()"   

class Parent1(object):
    def __init__(self):
        print "Parent1:: __init__()"
        super(Parent1, self).__init__()

class Parent2(object):
    def __init__(self):
        print "Parent2:: __init__()"
        super(Parent2, self).__init__()

@A
class B(Parent1, Parent2):
    def __init__(self):
        print "B::__init__()"
        super(B, self).__init__()

def main():
    b = B()

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

版本#3 - 输出:

A::__init__()
A::__call__()
B::__init__()
Traceback (most recent call last):
  File "so.py", line 40, in <module>
    main()
  File "so.py", line 36, in main
    b = B()
  File "so.py", line 10, in __call__
    return self._klass()
  File "so.py", line 32, in __init__
    super(B, self).__init__()
TypeError: must be type, not A
A::__del__()
Run Code Online (Sandbox Code Playgroud)

题:

版本#1仅供参考.它解释了我正在尝试做什么,即捕获creationdeletion对象class B.

在版本#2中,我尝试了相同的对象,class B这些对象是从中派生出来的Parent1,Parent2并且使用明确初始化,Parent1.__init__(self)并且Parent2.__init__(self)可以正常工作.

但是在版本#3中,我尝试了同样的super()方法.但我得到以下错误 - TypeError: must be type, not A.我认为这是因为链中__init__()所有父类的方法都MRO没有被正确调用 - 为什么?而且,我该如何解决这个问题?

use*_*ica 6

主要问题是super第一个参数需要是实际的类,但在版本3中,在

super(B, self)
Run Code Online (Sandbox Code Playgroud)

B不是你创建的类.它是A包装类的实例.你需要做类似的事情

class _B(Parent1, Parent2):
    def __init__(self):
        print "B::__init__()"
        super(_B, self).__init__()
B = A(_B)
Run Code Online (Sandbox Code Playgroud)

或者不是BA实例中包装,而是使用装饰器用包装器替换B's __init____del__方法而不替换整个B类.

此外,如果要跟踪B实例的删除,则__del__方法A将不会执行此操作.它将跟踪类的删除,而不是单个实例.


这是一个装饰器应该做你想要的,没有很多问题来自将类包装在非类的东西中:

def track_creation_and_deletion(klass):
    original_init = klass.__init__
    try:
        original_del = klass.__del__
    except AttributeError:
        def original_del(self):
            pass

    def new_init(self, *args, **kwargs):
        print '{}.{}.__init__'.format(klass.__module__, klass.__name__)
        return original_init(self, *args, **kwargs)
    def new_del(self):
        print '{}.{}.__del__'.format(klass.__module__, klass.__name__)
        return original_del(self)

    # functools.wraps doesn't play nicely with built-in methods,
    # so we handle it ourselves
    new_init.__name__ = '__init__'
    new_init.__doc__ = original_init.__doc__
    new_init.__module__ = klass.__module__
    new_init.__dict__.update(getattr(original_init, '__dict__', {}))

    new_del.__name__ = '__del__'
    new_del.__doc__ = original_del.__doc__
    new_del.__module__ = klass.__module__
    new_del.__dict__.update(getattr(original_del, '__dict__', {}))

    klass.__init__ = new_init
    klass.__del__ = new_del

    return klass
Run Code Online (Sandbox Code Playgroud)

其中大约一半是错误处理和复制一些元数据,使新方法看起来像是由调用者定义的.关键部分是我们定义新的__init____del__方法包装并替换类的旧的.当创建一个装饰类的实例时,__init__我们给它的方法将调用我们选择的日志代码.当一个装饰类的实例被垃圾收集时,__del__我们给它的方法将调用其他日志代码.由于我们没有替换类对象本身,因此在super调用中按名称引用类将引用它们需要引用的类.

这种方法的一个限制是很难在我们中检查实例本身__init__,因为即使在包装__init__返回之后它也可能没有完全构造.例如,如果我们尝试print实例,我们可能会触发子类的__str__方法依赖于尚未准备好的子类属性,从而导致AttributeError.