【问题标题】:TypeError when using super method with class decorator for a derived class将超级方法与派生类的类装饰器一起使用时出现类型错误
【发布时间】:2014-03-16 14:49:23
【问题描述】:

首先,对冗长的解释表示歉意。

版本 #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 仅供参考。它解释了我想要做什么,即捕获class B 的对象的creationdeletion

在版本 #2 中,我尝试了相同的 class B 对象,该对象派生自 Parent1Parent2,它们使用 Parent1.__init__(self)Parent2.__init__(self) 显式初始化,可以正常工作。

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

【问题讨论】:

标签: python decorator python-decorators


【解决方案1】:

主要问题是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__init____del__ 方法,而不用替换整个B 类。

此外,如果您想跟踪 B 实例的删除,A 上的 __del__ 方法不会这样做。它将跟踪类的删除,而不是单个实例。


这是一个装饰器,它应该可以做你想做的事,而不会因为将类包装在非类中而产生许多问题:

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 实例,我们可能会触发子类的 __str__ 方法,该方法依赖于尚未准备好的子类属性,从而导致 AttributeError。

【讨论】:

  • +1 我正在考虑将self._klass 传递给__init__ 调用:self._klass(self._klass),然后在super 调用中使用它而不是B。但是,这看起来更好。
【解决方案2】:

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

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

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

我有以下示例,它可以模仿它。请仔细查看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

这是我可以为我的问题提出的最安全的解决方案。欢迎提出建议!

【讨论】:

    猜你喜欢
    • 2019-05-02
    • 2019-07-23
    • 1970-01-01
    • 2017-09-25
    • 1970-01-01
    • 1970-01-01
    • 2020-05-06
    • 2014-01-14
    • 1970-01-01
    相关资源
    最近更新 更多