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

时间:2014-02-17 10:42:21

标签: 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()

版本#1 - 输出:

A::__init__()
A::__call__()
B::__init__()
A::__del__()

版本#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()

版本#2 - 输出:

A::__init__()
A::__call__()
B::__init__()
Parent1:: __init__()
Parent2:: __init__()
Parent2:: __init__()
A::__del__()

版本#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()

版本#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__()

问题:

版本#1仅供参考。它解释了我想要做的事情,即捕获creation对象的deletionclass B

在版本#2中,我尝试使用class BParent1派生的Parent2对象,这些对象使用Parent1.__init__(self)和{{显式初始化1}}按预期工作正常。

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

2 个答案:

答案 0 :(得分:6)

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

super(B, self)

B不是您创建的课程。这是包装类的A实例。您需要执行类似

的操作
class _B(Parent1, Parent2):
    def __init__(self):
        print "B::__init__()"
        super(_B, self).__init__()
B = A(_B)

或者不是在B实例中包装A,而是使用装饰器将B的{​​{1}}和__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 __init__方法包装和替换类的旧方法。创建装饰类的实例时,我们给它的__del__方法将调用我们选择的日志代码。当装饰类的实例被垃圾收集时,我们给它的__init__方法将调用其他日志代码。由于我们没有替换类对象本身,因此在__del__调用中按名称引用类将引用它们需要引用的类。

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

答案 1 :(得分:0)

我花了一些时间来理解为什么难以分别使用__call____del__方法捕获对象实例化和对象删除。以下是一些有用的参考资料

使用__del__方法可以使用优秀的黑客攻击,但它们有副作用!例如,@ user2357112给出的答案是一个很好的黑客,但是当我们进行循环引用时它不起作用,因为垃圾收集器无法确定首先调用的循环引用中的哪个__del__!然而,这可以通过使用弱参考来避免;但它仍然是一个黑客!

其中一个建议是创建一个上下文管理器,它可以创建和删除特定类的对象。

我有以下示例,哪种模拟它。请仔细查看Controller装饰器。

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__()

def Controller(_cls):
    class Wrapper(_cls):
        def create(self, name):
            ret = _cls.create(self, name)
            print "Added to Database! :: ", name
            # Database add here!
            return ret

        def remove(self, name):
            ret = _cls.remove(self, name)
            print "Deleted from Database! :: ", name
            # Database delete here!
            return ret
    return Wrapper

@Controller
class Manager(object):
    def __init__(self):
        #print "Manager::__init__()"
        self._repo = []

    def create(self, name):
        a = A(name)
        print "Object created :: ", name
        self._repo.append(a)

    def remove(self, name):
        for i, item in enumerate(self._repo):
            if item._name == name:
                del self._repo[i]
                print "Object removed :: ", name

    def display(self):
        for item in self._repo:
            print item

class A(Parent1, Parent2):
    def __init__(self, name):
        #print "A::__init__()"
        self._name = name
        super(A, self).__init__()

    def __repr__(self):
        return self._name

def main():
    m1 = Manager()
    m1.create("apples")
    m1.create("oranges")
    m1.create("grapes")
    #m1.display()
    m1.remove("apples")
    #m1.display()

if __name__ == "__main__":
    main()

执行时,会产生以下结果:

Object created ::  apples
Added to Database! ::  apples
Object created ::  oranges
Added to Database! ::  oranges
Object created ::  grapes
Added to Database! ::  grapes
Object removed ::  apples
Deleted from Database! ::  apples

这是我能解决问题的最安全的解决方案。建议欢迎!