Python is a very popular programming language that comes with many pitfalls. This presentation describes some of these pitfalls, especially when they could trick unsuspecting object-oriented developers. It proposes solutions to these pitfalls, in particular regarding inheritance, which is easily broken because of the implementation choice of Python for explicit delegation, its method resolution order, and its use of the C3 algorithm. It discusses some advantages of using Python, especially regarding meta-classes.
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Some Pitfalls with Python and Their Possible Solutions v0.9
1. Yann-Gaël Guéhéneuc
This work is licensed under a Creative
Commons Attribution-NonCommercial-
ShareAlike 3.0 Unported License
Yann-Gaël Guéhéneuc
Python Pitfalls
yann-gael.gueheneuc@concordia.ca
Version 0.9
2024/01/14
2. 2/64
Outline
All attributes are dynamic
Everything is a method
Inheritance is just a suggestion
Metaclasses always come last
3. 3/64
Outline
All attributes are dynamic
Everything is a method
Inheritance is just a suggestion
Metaclasses always come last
5. 5/64
All Attributes Are Dynamic
class A:
pass
a = A()
print()
print("A.attr = "1"")
A.attr = "1"
print(f"A.attr = {A.attr} (id = {id(A.attr)})")
print(f"a.attr = {a.attr} (id = {id(a.attr)})")
print()
print("a.attr = "2"")
a.attr = "2"
print(f"A.attr = {A.attr} (id = {id(A.attr)})")
print(f"a.attr = {a.attr} (id = {id(a.attr)})")
6. 6/64
All Attributes Are Dynamic
class A:
pass
a = A()
print()
print("A.attr = "1"")
A.attr = "1"
print(f"A.attr = {A.attr} (id = {id(A.attr)})")
print(f"a.attr = {a.attr} (id = {id(a.attr)})")
print()
print("a.attr = "2"")
a.attr = "2"
print(f"A.attr = {A.attr} (id = {id(A.attr)})")
print(f"a.attr = {a.attr} (id = {id(a.attr)})")
A.attr = "1"
A.attr = 1 (id = 140736437823448)
a.attr = 1 (id = 140736437823448)
a.attr = "2"
A.attr = 1 (id = 140736437823448)
a.attr = 2 (id = 140736437823496)
7. 7/64
All Attributes Are Dynamic
Python automagically ⚙ assign the value
of a class attribute to the instance attribute of
the same name
8. 8/64
All Attributes Are Dynamic
class B:
pass
b = B()
print()
print("b.attr = "1"")
b.attr = "1"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
print()
print("b.attr = "1"")
B.attr = "2"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
9. 9/64
class B:
pass
b = B()
print()
print("b.attr = "1"")
b.attr = "1"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
try:
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
except:
print("AttributeError: type object 'B' has no attribute 'attr'")
print()
print("b.attr = "1"")
B.attr = "2"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
10. 10/64
class B:
pass
b = B()
print()
print("b.attr = "1"")
b.attr = "1"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
try:
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
except:
print("AttributeError: type object 'B' has no attribute 'attr'")
print()
print("b.attr = "1"")
B.attr = "2"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
b.attr = "1"
b.attr = 1 (id = 140736437823448)
<What error can it be?>
b.attr = "1"
b.attr = 1 (id = 140736437823448)
B.attr = 2 (id = 140736437823496)
11. 11/64
class B:
pass
b = B()
print()
print("b.attr = "1"")
b.attr = "1"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
try:
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
except:
print("AttributeError: type object 'B' has no attribute 'attr'")
print()
print("b.attr = "1"")
B.attr = "2"
print(f"b.attr = {b.attr} (id = {id(b.attr)})")
print(f"B.attr = {B.attr} (id = {id(B.attr)})")
b.attr = "1"
b.attr = 1 (id = 140736437823448)
AttributeError: type object 'B' has no attribute 'attr'
b.attr = "1"
b.attr = 1 (id = 140736437823448)
B.attr = 2 (id = 140736437823496)
12. 12/64
All Attributes Are Dynamic
Even popular questions with popular
answers on StackOverflow confuses
class and instance variables!
https://stackoverflow.com/questions/6760685/what-is-the-best-way-of-implementing-singleton-in-python
13. 13/64
All Attributes Are Dynamic
Read/Write accesses on classes behave as
expected in any other language
Write accesses on instances behave
differently and shadow the class variable!
https://stackoverflow.com/questions/3434581/how-do-i-set-and-access-attributes-of-a-class
A.a_var1 = "New value for a_var1"
print(f"A.a_var1 = {A.a_var1} (id = {id(A.a_var1)})")
print(f"C.a_var1 = {C.a_var1} (id = {id(C.a_var1)})")
print(f"a1.a_var1 = {a1.a_var1} (id = {id(a1.a_var1)})")
print(f"a2.a_var1 = {a2.a_var1} (id = {id(a2.a_var1)})")
a1.a_var1 = "Another value for a_var1"
print(f"A.a_var1 = {A.a_var1} (id = {id(A.a_var1)})")
print(f"C.a_var1 = {C.a_var1} (id = {id(C.a_var1)})")
print(f"a1.a_var1 = {a1.a_var1} (id = {id(a1.a_var1)})")
print(f"a2.a_var1 = {a2.a_var1} (id = {id(a2.a_var1)})")
A.a_var1 = New value for a_var1 (id = 2238584427760)
C.a_var1 = New value for a_var1 (id = 2238584427760)
a1.a_var1 = New value for a_var1 (id = 2238584427760)
a2.a_var1 = New value for a_var1 (id = 2238584427760)
A.a_var1 = New value for a_var1 (id = 2238584427760)
C.a_var1 = New value for a_var1 (id = 2238584427760)
a1.a_var1 = Another value for a_var1 (id = 2238584286432)
a2.a_var1 = New value for a_var1 (id = 2238584427760)
14. 14/64
All Attributes Are Dynamic
Read/Write accesses on classes behave as
expected in any other language
Write accesses on instances behave
differently and shadow the class variable!
https://stackoverflow.com/questions/3434581/how-do-i-set-and-access-attributes-of-a-class
A.a_var1 = "New value for a_var1"
print(f"A.a_var1 = {A.a_var1} (id = {id(A.a_var1)})")
print(f"C.a_var1 = {C.a_var1} (id = {id(C.a_var1)})")
print(f"a1.a_var1 = {a1.a_var1} (id = {id(a1.a_var1)})")
print(f"a2.a_var1 = {a2.a_var1} (id = {id(a2.a_var1)})")
a1.a_var1 = "Another value for a_var1"
print(f"A.a_var1 = {A.a_var1} (id = {id(A.a_var1)})")
print(f"C.a_var1 = {C.a_var1} (id = {id(C.a_var1)})")
print(f"a1.a_var1 = {a1.a_var1} (id = {id(a1.a_var1)})")
print(f"a2.a_var1 = {a2.a_var1} (id = {id(a2.a_var1)})")
A.a_var1 = New value for a_var1 (id = 2238584427760)
C.a_var1 = New value for a_var1 (id = 2238584427760)
a1.a_var1 = New value for a_var1 (id = 2238584427760)
a2.a_var1 = New value for a_var1 (id = 2238584427760)
A.a_var1 = New value for a_var1 (id = 2238584427760)
C.a_var1 = New value for a_var1 (id = 2238584427760)
a1.a_var1 = Another value for a_var1 (id = 2238584286432)
a2.a_var1 = New value for a_var1 (id = 2238584427760)
Same name, but now
an instance variable!
15. 15/64
Outline
All attributes are dynamic
Everything is a method
Inheritance is just a suggestion
Metaclasses always come last
16. 16/64
Everything Is A Method
Python includes
– Instance methods
– Class methods
– Static methods
17. 17/64
Everything Is A Method
class A:
def instanceMethod(self):
print(f"A.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"A.classMethod({cls})")
@staticmethod
def staticMethod():
print("A.staticMethod()")
print("On A")
A.instanceMethod(A())
A.classMethod()
A.staticMethod()
print("On a = A()")
a = A()
a.instanceMethod()
a.classMethod()
a.staticMethod()
18. 18/64
Everything Is A Method
class A:
def instanceMethod(self):
print(f"A.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"A.classMethod({cls})")
@staticmethod
def staticMethod():
print("A.staticMethod()")
print("On A")
A.instanceMethod(A())
A.classMethod()
A.staticMethod()
print("On a = A()")
a = A()
a.instanceMethod()
a.classMethod()
a.staticMethod()
On A
A.instanceMethod(<__main__.A object at 0x...>)
A.classMethod(<class '__main__.A'>)
A.staticMethod()
On a = A()
A.instanceMethod(<__main__.A object at 0x...>)
A.classMethod(<class '__main__.A'>)
A.staticMethod()
19. 19/64
Everything Is A Method
class A:
def instanceMethod(self):
print(f"A.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"A.classMethod({cls})")
@staticmethod
def staticMethod():
print("A.staticMethod()")
print("On A")
A.instanceMethod(A())
A.classMethod()
A.staticMethod()
print("On a = A()")
a = A()
a.instanceMethod()
a.classMethod()
a.staticMethod()
On A
A.instanceMethod(<__main__.A object at 0x...>)
A.classMethod(<class '__main__.A'>)
A.staticMethod()
On a = A()
A.instanceMethod(<__main__.A object at 0x...>)
A.classMethod(<class '__main__.A'>)
A.staticMethod()
21. 21/64
class A:
def instanceMethod(self):
print(f"A.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"A.classMethod({cls})")
@staticmethod
def staticMethod():
print("A.staticMethod()")
class B(A):
def instanceMethod(self):
print(f"B.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"B.classMethod({cls})")
@staticmethod
def staticMethod():
print("B.staticMethod()")
print("On B")
B.instanceMethod(B())
B.instanceMethod(A())
B.classMethod()
B.staticMethod()
print("On b = B()")
b = B()
b.instanceMethod()
b.classMethod()
b.staticMethod()
On B
B.instanceMethod(<__main__.B object at 0x...>)
B.instanceMethod(<__main__.A object at 0x...>)
B.classMethod(<class '__main__.B'>)
B.staticMethod()
On b = B()
B.instanceMethod(<__main__.B object at 0x...>)
B.classMethod(<class '__main__.B'>)
B.staticMethod()
22. 22/64
class A:
def instanceMethod(self):
print(f"A.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"A.classMethod({cls})")
@staticmethod
def staticMethod():
print("A.staticMethod()")
class B(A):
def instanceMethod(self):
print(f"B.instanceMethod({self})")
@classmethod
def classMethod(cls):
print(f"B.classMethod({cls})")
@staticmethod
def staticMethod():
print("B.staticMethod()")
print("On B")
B.instanceMethod(B())
B.instanceMethod(A())
B.classMethod()
B.staticMethod()
print("On b = B()")
b = B()
b.instanceMethod()
b.classMethod()
b.staticMethod()
On B
B.instanceMethod(<__main__.B object at 0x...>)
B.instanceMethod(<__main__.A object at 0x...>)
B.classMethod(<class '__main__.B'>)
B.staticMethod()
On b = B()
B.instanceMethod(<__main__.B object at 0x...>)
B.classMethod(<class '__main__.B'>)
B.staticMethod()
23. 23/64
Everything Is A Method
All methods are overloadable
Class methods are methods
Therefore, class methods are overloadable
Same goes for static methods!
https://en.wikipedia.org/wiki/Syllogism
24. 24/64
Everything Is A Method
The decorations @classmethod and
@staticmethod are decorators
The decorations @classmethod and
@staticmethod are about bindings
– Not about the receiver / call site
– Not the object model (i.e., metaclasses)
25. 25/64
Everything Is A Method
class C(A):
def instanceMethod(self):
print(f"C.instanceMethod({self})")
super().instanceMethod()
def classMethod(self):
print(f"C.classMethod({self})")
super().classMethod()
def staticMethod(self):
print("C.staticMethod()")
super().staticMethod()
print("On C")
C.instanceMethod(C())
C.instanceMethod(A())
C.classMethod(C())
C.staticMethod(C())
print("On c = C()")
c = C()
c.instanceMethod()
c.classMethod()
c.staticMethod()
26. 26/64
Everything Is A Method
class C(A):
def instanceMethod(self):
print(f"C.instanceMethod({self})")
super().instanceMethod()
def classMethod(self):
print(f"C.classMethod({self})")
super().classMethod()
def staticMethod(self):
print("C.staticMethod()")
super().staticMethod()
print("On C")
C.instanceMethod(C())
C.instanceMethod(A())
C.classMethod(C())
C.staticMethod(C())
print("On c = C()")
c = C()
c.instanceMethod()
c.classMethod()
c.staticMethod()
No more decorations
All instance methods
27. 27/64
Everything Is A Method
class C(A):
def instanceMethod(self):
print(f"C.instanceMethod({self})")
try:
super().instanceMethod()
except:
print("TypeError: super(type, obj): obj must be an instance or subtype of type")
def classMethod(self):
print(f"C.classMethod({self})")
super().classMethod()
def staticMethod(self):
print("C.staticMethod()")
super().staticMethod()
print("On C")
C.instanceMethod(C())
C.instanceMethod(A())
C.classMethod(C())
C.staticMethod(C())
print("On c = C()")
c = C()
c.instanceMethod()
c.classMethod()
c.staticMethod()
28. 28/64
Everything Is A Method
class C(A):
def instanceMethod(self):
print(f"C.instanceMethod({self})")
try:
super().instanceMethod()
except:
print("TypeError: super(type, obj): obj must be an instance or subtype of type")
def classMethod(self):
print(f"C.classMethod({self})")
super().classMethod()
def staticMethod(self):
print("C.staticMethod()")
super().staticMethod()
print("On C")
C.instanceMethod(C())
C.instanceMethod(A())
C.classMethod(C())
C.staticMethod(C())
print("On c = C()")
c = C()
c.instanceMethod()
c.classMethod()
c.staticMethod()
On C
C.instanceMethod(<__main__.C object at 0x...>)
A.instanceMethod(<__main__.C object at 0x...>)
C.instanceMethod(<__main__.A object at 0x...>)
<What error can it be?>
C.classMethod(<__main__.C object at 0x...>)
A.classMethod(<class '__main__.C'>)
C.staticMethod()
A.staticMethod()
On c = C()
C.instanceMethod(<__main__.C object at 0x...>)
A.instanceMethod(<__main__.C object at 0x...>)
C.classMethod(<__main__.C object at 0x...>)
A.classMethod(<class '__main__.C'>)
C.staticMethod()
A.staticMethod()
29. 29/64
Everything Is A Method
class C(A):
def instanceMethod(self):
print(f"C.instanceMethod({self})")
try:
super().instanceMethod()
except:
print("TypeError: super(type, obj): obj must be an instance or subtype of type")
def classMethod(self):
print(f"C.classMethod({self})")
super().classMethod()
def staticMethod(self):
print("C.staticMethod()")
super().staticMethod()
print("On C")
C.instanceMethod(C())
C.instanceMethod(A())
C.classMethod(C())
C.staticMethod(C())
print("On c = C()")
c = C()
c.instanceMethod()
c.classMethod()
c.staticMethod()
On C
C.instanceMethod(<__main__.C object at 0x...>)
A.instanceMethod(<__main__.C object at 0x...>)
C.instanceMethod(<__main__.A object at 0x...>)
TypeError: super(type, obj): obj must be an…
C.classMethod(<__main__.C object at 0x...>)
A.classMethod(<class '__main__.C'>)
C.staticMethod()
A.staticMethod()
On c = C()
C.instanceMethod(<__main__.C object at 0x...>)
A.instanceMethod(<__main__.C object at 0x...>)
C.classMethod(<__main__.C object at 0x...>)
A.classMethod(<class '__main__.C'>)
C.staticMethod()
A.staticMethod()
30. 30/64
Everything Is A Method
Python 3 super() is equivalent to Python 2
super(__class__, <firstarg>)
– “where __class__ is the class [in which] the
method was defined, and <firstarg> is the first
parameter of the method (normally self for
instance methods, and cls for class methods).”
Contravariance on <firstarg>
– Obviously! ᦓ
https://peps.python.org/pep-3135/
31. 31/64
Everything Is A Method
Contravariance on <firstarg>
class C(A):
...
class D(C):
pass
print("On C")
C.instanceMethod(C())
C.instanceMethod(D())
C.classMethod(C())
C.staticMethod(C())
print("On d = D()")
d = D()
d.instanceMethod()
d.classMethod()
d.staticMethod()
32. 32/64
Everything Is A Method
Contravariance on <firstarg>
class C(A):
...
class D(C):
pass
print("On C")
C.instanceMethod(C())
C.instanceMethod(D())
C.classMethod(C())
C.staticMethod(C())
print("On d = D()")
d = D()
d.instanceMethod()
d.classMethod()
d.staticMethod()
On C
C.instanceMethod(<__main__.C object at 0x...>)
A.instanceMethod(<__main__.C object at 0x...>)
C.instanceMethod(<__main__.D object at 0x...>)
A.instanceMethod(<__main__.D object at 0x...>)
C.classMethod(<__main__.C object at 0x...>)
A.classMethod(<class '__main__.C'>)
C.staticMethod()
A.staticMethod()
On d = D()
C.instanceMethod(<__main__.D object at 0x...>)
A.instanceMethod(<__main__.D object at 0x...>)
C.classMethod(<__main__.D object at 0x...>)
A.classMethod(<class '__main__.D'>)
C.staticMethod()
A.staticMethod()
33. 33/64
Outline
All attributes are dynamic
Everything is a method
Inheritance is just a suggestion
Metaclasses always come last
34. 34/64
Inheritance Is Just A Suggestion
Java
– “[The] super keyword is used to access methods of the parent class
while this is used to access methods of the current class.”
Smalltalk
– “self is used when an object wishes to refer to itself, and super is
used to refer to the superclass of the object.”
C++
– “this is a keyword that refers to the current instance of the class.”
– There is no super keyword in (standard) C++
Python
– “self is a reference to the object instance […]. super allows you to
access attributes (methods, members, etc.) of an ancestor type.”
https://www.geeksforgeeks.org/super-and-this-keywords-in-java/
https://courses.cs.washington.edu/courses/cse505/99au/oo/smalltalk-concepts.html
https://www.javatpoint.com/cpp-this-pointer
https://stackoverflow.com/questions/72705781/difference-between-self-and-super
35. 35/64
Inheritance Is Just A Suggestion
Single inheritance
– Java
– Smalltalk
super refers to the (direct) superclass of a
class so an object can access the methods
and fields of the superclass of its class
36. 36/64
Inheritance Is Just A Suggestion
Multiple inheritance
– C++
– Python
Two different approaches
– C++ Ἢ
– Python ⏏
37. 37/64
Inheritance Is Just
A Suggestion
C++ Ἢ
– virtual keyword in
base clause
– Base initialisation in the
member initializer list
class Object {
public:
Object(int c) {
printf("Objectn");
a = 0;
}
int a;
};
class Object1 : public virtual Object {
public:
Object1(int c) : Object(c) {
printf("Object1n");
a1 = 0;
a = 1;
}
int a1;
};
class Object2 : public virtual Object {
public:
Object2(int c) : Object(c) {
printf("Object2n");
a2= 0;
a = 2;
}
int a2;
};
class Object4 : public Object1, public Object2 { public:
Object4(int c) : Object(c), Object1(c), Object2(c) {
printf("Object4n");
a4 = 0;
a = 4;
}
int a4;
};
https://cplusplus.com/forum/general/1414/
https://cplusplus.com/forum/general/1420/
38. 38/64
Inheritance Is Just
A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://dl.acm.org/doi/10.1145/236337.236343
https://www.python.org/download/releases/2.3/mro/
class A:
def __init__(self):
print("A")
super().__init__()
def output(self):
print("A.output()")
class B(A):
def __init__(self):
print("B")
super().__init__()
def output(self):
print("B.output()")
super().output()
class C(B):
def __init__(self):
print("C")
super().__init__()
def output(self):
print("C.output()")
super().output()
class D(A):
def __init__(self):
print("D")
super().__init__()
def output(self):
print("D.output()")
super().output()
class E(C, D):
def __init__(self):
print("E")
super().__init__()
def output(self):
print("E.output()")
super().output()
39. 39/64
Inheritance Is Just
A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://dl.acm.org/doi/10.1145/236337.236343
https://www.python.org/download/releases/2.3/mro/
class A:
def __init__(self):
print("A")
super().__init__()
def output(self):
print("A.output()")
class B(A):
def __init__(self):
print("B")
super().__init__()
def output(self):
print("B.output()")
super().output()
class C(B):
def __init__(self):
print("C")
super().__init__()
def output(self):
print("C.output()")
super().output()
class D(A):
def __init__(self):
print("D")
super().__init__()
def output(self):
print("D.output()")
super().output()
class E(C, D):
def __init__(self):
print("E")
super().__init__()
def output(self):
print("E.output()")
super().output()
e = E()
e.output()
40. 40/64
Inheritance Is Just
A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://dl.acm.org/doi/10.1145/236337.236343
https://www.python.org/download/releases/2.3/mro/
class A:
def __init__(self):
print("A")
super().__init__()
def output(self):
print("A.output()")
class B(A):
def __init__(self):
print("B")
super().__init__()
def output(self):
print("B.output()")
super().output()
class C(B):
def __init__(self):
print("C")
super().__init__()
def output(self):
print("C.output()")
super().output()
class D(A):
def __init__(self):
print("D")
super().__init__()
def output(self):
print("D.output()")
super().output()
class E(C, D):
def __init__(self):
print("E")
super().__init__()
def output(self):
print("E.output()")
super().output()
e = E()
e.output()
E
C
B
D
A
E.output()
C.output()
B.output()
D.output()
A.output()
42. 42/64
Inheritance Is Just
A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
class A:
def __init__(self):
print("A")
super().__init__()
def output(self):
print("A.output()")
class B(A):
def __init__(self):
print("B")
super().__init__()
def output(self):
print("B.output()")
super().output()
class C(B):
def __init__(self):
print("C")
super().__init__()
def output(self):
print("C.output()")
super().output()
class D(A):
def __init__(self):
print("D")
super().__init__()
def output(self):
print("D.output()")
super().output()
class E(C, D):
def __init__(self):
print("E")
super().__init__()
def output(self):
print("E.output()")
super().output()
43. 43/64
Inheritance Is Just
A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
e = E()
e.output()
class A:
def __init__(self):
print("A")
super().__init__()
def output(self):
print("A.output()")
class B(A):
def __init__(self):
print("B")
super().__init__()
def output(self):
print("B.output()")
super().output()
class C(B):
def __init__(self):
print("C")
super().__init__()
def output(self):
print("C.output()")
super().output()
class D(A):
def __init__(self):
print("D")
super().__init__()
def output(self):
print("D.output()")
super().output()
class E(C, D):
def __init__(self):
print("E")
super().__init__()
def output(self):
print("E.output()")
super().output()
44. 44/64
Inheritance Is Just
A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
e = E()
e.output()
class A:
def __init__(self):
print("A")
super().__init__()
def output(self):
print("A.output()")
class B(A):
def __init__(self):
print("B")
super().__init__()
def output(self):
print("B.output()")
super().output()
class C(B):
def __init__(self):
print("C")
super().__init__()
def output(self):
print("C.output()")
super().output()
class D(A):
def __init__(self):
print("D")
super().__init__()
def output(self):
print("D.output()")
super().output()
class E(C, D):
def __init__(self):
print("E")
super().__init__()
def output(self):
print("E.output()")
super().output()
E
C
B
D
A
E.output()
C.output()
B.output()
D.output()
A.output()
45. 45/64
Inheritance Is Just A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://news.ycombinator.com/item?id=24255334
class A(object):
def __init__(self):
self.x = 1
class B(object):
def __init__(self):
self.y = 2
class C(A, B):
pass
print(C().y)
46. 46/64
Inheritance Is Just A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://news.ycombinator.com/item?id=24255334
class A(object):
def __init__(self):
self.x = 1
class B(object):
def __init__(self):
self.y = 2
class C(A, B):
pass
print(C().y)
Traceback (most recent call last):
File “…Inheritance2.py", line 14, in <module>
print(C().y)
^^^^^
AttributeError: 'C' object has no attribute 'y'
47. 47/64
Inheritance Is Just A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://news.ycombinator.com/item?id=24255334
class A(object):
def __init__(self):
super().__init__()
self.x = 1
class B(object):
def __init__(self):
self.y = 2
class C(A, B):
pass
print(C().y)
48. 48/64
Inheritance Is Just A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
https://news.ycombinator.com/item?id=24255334
class A(object):
def __init__(self):
super().__init__()
self.x = 1
class B(object):
def __init__(self):
self.y = 2
class C(A, B):
pass
print(C().y)
2
49. 49/64
Inheritance Is Just A Suggestion
Python ⏏
– Method Resolution Order
• C3 algorithm
Breaks (at least) two principles
– Principle of least astonishment / surprise
– Principle of locality
50. 50/64
Inheritance Is Just A Suggestion
Principle of least astonishment / surprise
– “Transparency is a passive quality. A program is
transparent when it is possible to form a simple
mental model of its behavior that is actually
predictive for all or most cases, because you
can see through the machinery to what is
actually going on.” – Eric Raymond
(Emphasis mine)
https://wiki.c2.com/?PrincipleOfLeastAstonishment
51. 51/64
Inheritance Is Just A Suggestion
Principle of locality
– “[A]n error is local in time if it is discovered very
soon after it is created; an error is local in
space if it is identified very close (at) the site
where the error actually resides.”
(Emphasis mine)
https://beza1e1.tuxen.de/articles/principle_of_locality.html
https://wiki.c2.com/?CeeVsAdaStudy
52. 52/64
Inheritance Is Just A Suggestion
https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance
53. 53/64
Inheritance Is Just A Suggestion
https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance
Inheritance in Python involves explicit
delegations and an obscure algorithm
54. 54/64
Inheritance Is Just A Suggestion
https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance
Inheritance in Python involves explicit
delegations and an obscure algorithm
Exercise utmost caution
55. 55/64
Outline
All attributes are dynamic
Everything is a method
Inheritance is just a suggestion
Metaclasses always come last
58. 58/64
Metaclasses Always Come Last
Caveats
– Cannot have both a class and an instance
__new__() method in the same class
– The class Object defines a static (à la Python)
__new__() method that hides any __new__()
method from a metaclass
59. 59/64
Metaclasses Always Come Last
Very promising…
…But limited by dynamicity of Python ⠦
Workaround with __call__()
60. 60/64
Metaclasses Always Come Last
Class creation
– __new__() instantiates a
class
– __init__() initialises
variables
– __prepare__() defines
the class namespace
passed to the metaclass
__new__ and __init__
methods
Instance creation
– __call__() invoked
after the __new__ and
__init__
– Only because classes
are callable objects
• Instance of a class with a
__call__ method
• Anything with a non-null
tp_call (C struct)
https://elfi-y.medium.com/python-metaclass-7cb56510845
https://stackoverflow.com/questions/111234/what-is-a-callable
61. 61/64
Metaclasses Always Come Last
Using __call__() for reference counting
class ReferenceCountingMetaClass(type):
def __init__(self, name, bases, namespace):
self._instances = 0
def __call__(self):
newInstance = super().__call__()
self._instances = self._instances + 1
return newInstance
def getNumberOfInstances(self):
return self._instances
62. 62/64
Metaclasses Always Come Last
Using __call__() for reference counting
class ReferenceCountingMetaClass(type):
def __init__(self, name, bases, namespace):
self._instances = 0
def __call__(self):
newInstance = super().__call__()
self._instances = self._instances + 1
return newInstance
def getNumberOfInstances(self):
return self._instances
Override the __call__ metaclass instance method
Define the get…() metaclass instance method
63. 63/64
Metaclasses Always Come Last
Using __call__() for reference counting
class C(metaclass=ReferenceCountingMetaClass):
pass
class D():
pass
x = C()
print(C.getNumberOfInstances())
y = C()
print(C.getNumberOfInstances())
z = C()
print(C.getNumberOfInstances())
x = D()
y = D()
64. 64/64
Metaclasses Always Come Last
Using __call__() for reference counting
class C(metaclass=ReferenceCountingMetaClass):
pass
class D():
pass
x = C()
print(C.getNumberOfInstances())
y = C()
print(C.getNumberOfInstances())
z = C()
print(C.getNumberOfInstances())
x = D()
y = D()
1
2
3