Hériter des caractéristiques d'une autre classe¶
L'héritage permet de spécifier une classe sans tout réécrire.
Imaginez que vous écriviez un programme de modélisation 3D, vous allez avoir besoin de points, de cubes, de sphères, de rectangles et de nombreuses autres formes. Chaque forme doit être un objet et toutes les formes doivent avoir des propriétés telles qu'une position, une dimension et un nom. Par conséquent, il peut être utile de définir une classe avec toutes ces propriétés communes:
class Shape:
def __init__(self, dimension, position = [0,0], name = None):
self.dimension = dimension
self.position = position
if dimension == 3 and len(position) == 2: # a correction to incomplete data
self.position.append(0)
self.name = name
class Rectangle(Shape):
def __init__(self, width, length):
super().__init__(2, name = "Rectangle" ) # super() is the class we inherite from
self.width = width
self.length = length
def surface(self):
return self.width * self.length
r = Rectangle(3,2)
print(r.name, r.position, r.width, r.length)
Rectangle [0, 0] 3 2
Une classe enfant hérite de tous les attributs de son parent. Cependant, un objet parent doit être créé pour stocker ces attributs :
- un objet Shape a un attribut
dimension
uniquement si la méthode Shape__init__
est appelée - toute classe qui hérite de Rectangle hérite de sa méthode
surface ()
, mais il peut être utile de la redéfinir localement (elle s'appelle la spécialisation).
L'héritage est transitif, vous obtenez des propriétés de votre parent, de vos grands-parents, etc. (Python permet de casser la transitivité mais c'est très rare). Ici, Square
hérite de Rectangle
qui hérite de Shape
:
class Square(Rectangle):
def __init__(self, width):
super().__init__(width, width)
self.name = "Square" # since the line above made it "Rectangle"
q = Square(5)
print(q.name, q.position, q.width, q.length) # we still have q.length because of the inheritance
print(q.surface())
Square [0, 0] 5 5 25
Héritage multiple¶
Voyons comment nous héritons de plus d'une classe et quels sont les problèmes.
class A():
def myclass(self):
print("defined in A, I am a", self.__class__)
class B():
def myclass(self):
print("defined in B, I am a", self.__class__)
class C1(A,B): # double inheritance
pass # which method myclass will it use?
class C2(B,A):
pass
c = C1()
c.myclass()
c = C2()
c.myclass()
defined in A, I am a <class '__main__.C1'> defined in B, I am a <class '__main__.C2'>
On peut connaitre l'héritage d'une classe avec la méthode mro()
pour Method Resolution Order :
print(C1.mro()) # the mother of all classes is object, even if it is not shown in the declaration
[<class '__main__.C1'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# The diamond problem
#
# Which method of A, B and C will D use if not redefined?
#
# A
# / \
# B C
# \/
# D
class A(object):
def f(self):
print("f is defined in A, my class is", self.__class__)
class B(A):
pass
class C(A):
def f(self): # we redefine method inheritates from A
print("f is defined in C, my class is", self.__class__)
class D(B,C): # double inheritance with B first
pass
d = D()
d.f()
D.mro()
f is defined in C, my class is <class '__main__.D'>
[__main__.D, __main__.B, __main__.C, __main__.A, object]
Ordres des méthodes héritées¶
Pour pouvoir choisir la méthode héritées parmi celles des parents, grand-parents etc, on utilise une technique
dite de linéarisation qui consiste à ordonner les classes héritées (ce qui donne la liste produite par mro()
).
Pour cela on utilise l'algorithme de linéarisation C3.
Le principe de base est que la linéarisation de L(D)
qui hérite de B
et C
est :
L(D) = D + merge( L(B), L(C), [B, C])
avec la fusion (merge) des listes qui choisit dans l'ordre le premier éléments des listes si cet élément n'est pas
dans la queue (l[1:]
) d'autres listes.
Lorsqu'on prend un élément, on le retire de toutes les listes. Cela donne dans notre cas :
L(D) = D + merge([B, A, O], [C, A, O], [B, C])
= [D, B] + merge([A, O], [C, A, O], [C]) # A n'est pas bon car dans la queue de [C, A, O]
= [D, B, C] + merge([A, O], [A, O])
= [D, B, C, A] + merge([O], [O])
= [D, B, C, A, O]
On voit que C est avant A donc c'est la méthode de C qui est utilisée. Si B avait défini une méthode, cela sera la sienne qui serait choisie.
Pour résumer on a :
- les enfants précèdent leurs parents communs (donc B et C avant leur parent A)
- les enfants sont conservés dans l'ordre spécifié dans le tuple des classes de base (donc B avant C)
Note : Si C n'hérite pas de A alors on utilise f
de B avant f
de C et donc f
de A :
class C():
def f(self):
print("f is defined in C, my class is", self.__class__)
class D(B,C):
pass
d = D()
d.f()
D.mro()
f is defined in A, my class is <class '__main__.D'>
[__main__.D, __main__.B, __main__.A, __main__.C, object]
Appel direct d'une méthode d'une classe parente¶
On aussi avoir l'ordre initial D(B,C)
et appeler la fonction f
de A à la main avec A.f(self)
:
class C(A):
def f(self):
print("f is defined in C, my class is", self.__class__)
class D(B,C):
def f(self):
A.f(self) # if we don't do that, C.f(self) will be called
d = D()
d.f()
D.mro()
f is defined in A, my class is <class '__main__.D'>
[__main__.D, __main__.B, __main__.C, __main__.A, object]
C.f(d) # direct call to f of class C for object d
f is defined in C, my class is <class '__main__.D'>
{{ PreviousNext("01 Classes and objects.ipynb", "03 Scope.ipynb")}}