关于python:对于派生类使用带有类装饰器的超类方法时的TypeError

TypeError when using super method with class decorator for a derived class

首先,为冗长的解释道歉。

版本1-代码:类的类修饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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-输出:

1
2
3
4
A::__init__()
A::__call__()
B::__init__()
A::__del__()

号版本2-代码:显式初始化基类的派生类的类修饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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-输出:

1
2
3
4
5
6
7
A::__init__()
A::__call__()
B::__init__()
Parent1:: __init__()
Parent2:: __init__()
Parent2:: __init__()
A::__del__()

。版本3-代码:带有super()的派生类的类修饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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-输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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仅供参考。它解释了我要做的事情,即捕获creationdeletionclass B对象。

在第2版中,我也尝试过同样的方法来处理class B的对象,该对象是从Parent1Parent2派生的,它们是使用Parent1.__init__(self)Parent2.__init__(self)显式初始化的,它们工作得很好。

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


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

1
super(B, self)

B不是您创建的类。它是包装类的A实例。你需要做点什么

1
2
3
4
5
class _B(Parent1, Parent2):
    def __init__(self):
        print"B::__init__()"
        super(_B, self).__init__()
B = A(_B)

或者使用一个不替换整个B类的包装器来替换B__init____del__方法,而不是在A实例中包装B

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

这里有一个修饰符,它应该做您想要做的事情,而不存在将类包装成非类的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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。


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

  • http://eli.thegreenplace.net/2009/06/12/saferry-using-destructors-in-python/
  • 我不理解这种Python的行为
  • 同样的主题也在python list mailer中讨论过-在这里查看线程-https://mail.python.org/pipermail/python-list/2014-february/667592.html

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

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

我有下面的例子,哪种类型的模拟。请仔细看一下Controller装饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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()

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

1
2
3
4
5
6
7
8
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

这是我能想到的最安全的解决方法。欢迎提出建议!