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/64
Outline
 All attributes are dynamic
 Everything is a method
 Inheritance is just a suggestion
 Metaclasses always come last
3/64
Outline
 All attributes are dynamic
 Everything is a method
 Inheritance is just a suggestion
 Metaclasses always come last
4/64
All Attributes Are Dynamic
 Python allows the dynamic creation of
attributes
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/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/64
All Attributes Are Dynamic
 Python automagically ⚙ assign the value
of a class attribute to the instance attribute of
the same name
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/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/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/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/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/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/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/64
Outline
 All attributes are dynamic
 Everything is a method
 Inheritance is just a suggestion
 Metaclasses always come last
16/64
Everything Is A Method
 Python includes
– Instance methods
– Class methods
– Static methods
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/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/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()
20/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()
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/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/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/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/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/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/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/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/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/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/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/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/64
Outline
 All attributes are dynamic
 Everything is a method
 Inheritance is just a suggestion
 Metaclasses always come last
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/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/64
Inheritance Is Just A Suggestion
 Multiple inheritance
– C++
– Python
 Two different approaches
– C++ Ἢ
– Python ⏏
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/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/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/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()
41/64
Inheritance Is Just A Suggestion
 Python ⏏
– Method Resolution Order
• C3 algorithm
print(E.mro())
print(D.mro())
print(C.mro())
print(B.mro())
print(A.mro())
[<class '__main__.E'>, <class ‘….C'>, <class ‘….B'>, <class ‘….D'>, <class ‘….A'>, <class 'object'>]
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.A'>, <class 'object'>]
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/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/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/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/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/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/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/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/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/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/64
Inheritance Is Just A Suggestion
https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance
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/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/64
Outline
 All attributes are dynamic
 Everything is a method
 Inheritance is just a suggestion
 Metaclasses always come last
56/64
Metaclasses Always Come Last
https://blog.invisivel.net/2012/04/10/pythons-object-model-explained/
57/64
Metaclasses
Always
Come
Last
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/64
Metaclasses Always Come Last
 Very promising…
…But limited by dynamicity of Python ⠦
 Workaround with __call__()
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/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/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/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
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

Some Pitfalls with Python and Their Possible Solutions v0.9

  • 1.
    Yann-Gaël Guéhéneuc This workis 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 attributesare dynamic  Everything is a method  Inheritance is just a suggestion  Metaclasses always come last
  • 3.
    3/64 Outline  All attributesare dynamic  Everything is a method  Inheritance is just a suggestion  Metaclasses always come last
  • 4.
    4/64 All Attributes AreDynamic  Python allows the dynamic creation of attributes
  • 5.
    5/64 All Attributes AreDynamic 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 AreDynamic 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 AreDynamic  Python automagically ⚙ assign the value of a class attribute to the instance attribute of the same name
  • 8.
    8/64 All Attributes AreDynamic 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 AreDynamic  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 AreDynamic  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 AreDynamic  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 attributesare dynamic  Everything is a method  Inheritance is just a suggestion  Metaclasses always come last
  • 16.
    16/64 Everything Is AMethod  Python includes – Instance methods – Class methods – Static methods
  • 17.
    17/64 Everything Is AMethod 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 AMethod 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 AMethod 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()
  • 20.
    20/64 class A: def instanceMethod(self): print(f"A.instanceMethod({self})") @classmethod defclassMethod(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()
  • 21.
    21/64 class A: def instanceMethod(self): print(f"A.instanceMethod({self})") @classmethod defclassMethod(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 defclassMethod(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 AMethod  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 AMethod  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 AMethod 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 AMethod 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 AMethod 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 AMethod 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 AMethod 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 AMethod  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 AMethod  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 AMethod  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 attributesare dynamic  Everything is a method  Inheritance is just a suggestion  Metaclasses always come last
  • 34.
    34/64 Inheritance Is JustA 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 JustA 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 JustA Suggestion  Multiple inheritance – C++ – Python  Two different approaches – C++ Ἢ – Python ⏏
  • 37.
    37/64 Inheritance Is Just ASuggestion  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 ASuggestion  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 ASuggestion  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 ASuggestion  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()
  • 41.
    41/64 Inheritance Is JustA Suggestion  Python ⏏ – Method Resolution Order • C3 algorithm print(E.mro()) print(D.mro()) print(C.mro()) print(B.mro()) print(A.mro()) [<class '__main__.E'>, <class ‘….C'>, <class ‘….B'>, <class ‘….D'>, <class ‘….A'>, <class 'object'>] [<class '__main__.D'>, <class '__main__.A'>, <class 'object'>] [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>] [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>] [<class '__main__.A'>, <class 'object'>]
  • 42.
    42/64 Inheritance Is Just ASuggestion  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 ASuggestion  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 ASuggestion  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 JustA 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 JustA 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 JustA 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 JustA 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 JustA 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 JustA 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 JustA 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 JustA Suggestion https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance
  • 53.
    53/64 Inheritance Is JustA 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 JustA 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 attributesare dynamic  Everything is a method  Inheritance is just a suggestion  Metaclasses always come last
  • 56.
    56/64 Metaclasses Always ComeLast https://blog.invisivel.net/2012/04/10/pythons-object-model-explained/
  • 57.
  • 58.
    58/64 Metaclasses Always ComeLast  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 ComeLast  Very promising… …But limited by dynamicity of Python ⠦  Workaround with __call__()
  • 60.
    60/64 Metaclasses Always ComeLast  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 ComeLast  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 ComeLast  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 ComeLast  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 ComeLast  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