summaryrefslogtreecommitdiff
path: root/pypers/oxford/not_cooperative.txt
blob: 9017713c6127ed4475a0607c558a46948536c2c7 (plain)
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

Background: I am preparing my lecture for the Oxford ACCU conference
and I wanted to show how __getattribute__ can be used to trace
attribute access. Then I discovered that composing the TracedAttribute 
mixin with a built-in type does not work reliabily, where it works
perfectly fine with custom classes. 

Methods of builtin types are usually cooperative. 
Consider for instance this example:

#<cooperative.py>

class B1(object):
    def __init__(self, **kw):
        print "B1.__init__"
        super(B1, self).__init__(**kw)

class B2(object):
    def __init__(self, **kw):
        print "B2.__init__"
        super(B2, self).__init__(**kw)

#</cooperative.py>

>>> from cooperative import B1, B2
>>> class C(B1, B2, str):
...     pass
>>> c = C()
B1.__init__
B2.__init__

str.__init__, B1.__init__ and B2.__init__ are cooperative, as expected.
Moreover,

>>> class C(B1, str, B2):
...     pass
>>> c = C()
B1.__init__
B2.__init__

and

>>> class C(str, B1, B2):
...     pass
>>> c = C()
B1.__init__
B2.__init__

also work as expected.

However str.__getattribute__ (the same for int.__getattribute__)
is only apparently  cooperative with B1.__getattribute__ and 
B2.__getattribute__. Consider this other example:

#<trace_builtin.py>

class TracedAccess1(object):
    def __getattribute__(self, name):
        print "1: accessing %s" % name
        return super(TracedAccess1, self).__getattribute__(name)

class TracedAccess2(object):
    def __getattribute__(self, name):
        print "2: accessing %s" % name
        return super(TracedAccess2, self).__getattribute__(name)

class B(object):
    def __init__(self, *args):
        super(B, self).__init__(*args)


#</trace_builtin.py>

This *seems* to work:

>>> from trace_builtin import TracedAccess1, TracedAccess2
>>> class C(TracedAccess1, TracedAccess2, str):
...     pass
>>> cinit = C().__init__
1: accessing __init__
2: accessing __init__

However, if I change the order of the bases, I get
 
>>> class C(TracedAccess1, str, TracedAccess2):
...     pass
>>> cinit = C().__init__
1: accessing __init__

and

>>> class C(str, TracedAccess1, TracedAccess2):
...     pass
>>> cinit = C().__init__

so str.__getattribute__ (or int.__getattribute__) is not
cooperative.

There is no such a problem for a custom type, such as B:

>>> from trace_builtin import B
>>> class C(TracedAccess1, TracedAccess2, B):
...     pass
>>> cinit = C().__init__
1: accessing __init__
2: accessing __init__

>>> class C(TracedAccess1, B, TracedAccess2):
...     pass
>>> cinit = C().__init__
1: accessing __init__
2: accessing __init__

>>> class C(B, TracedAccess1, TracedAccess2):
...     pass
>>> cinit = C().__init__
1: accessing __init__
2: accessing __init__

Here, everything works fine. Can somebody share some light on this?