Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Complément - niveau intermédiaire

La classe object

Le symbole object est une variable prédéfinie (qui donc fait partie du module builtins) :

object
object
import builtins

builtins.object is object
True

La classe object est une classe spéciale ; toutes les classes en Python héritent de la classe object, même lorsqu’aucun héritage n’est spécifié :

class Foo:
    pass

Foo.__bases__
(object,)

L’attribut spécial __bases__, comme on le devine, nous permet d’accéder aux superclasses directes, ici de la classe Foo.

En Python moderne, on n’a jamais besoin de mentionner object dans le code. La raison de sa présence dans les symboles prédéfinis est liée à l’histoire de Python, et à la distinction que faisait Python 2 entre classes old-style et classes new-style. Nous le mentionnons seulement car on rencontre encore parfois du code qui fait quelque chose comme :

# ceci est du vieux code, on n'a pas besoin
# de faire hériter Bar de object
class Bar(object):
    pass

qui est un reste de Python 2, et que Python 3 accepte uniquement au titre de la compatibilité.

Complément - niveau avancé

Rappels

L’héritage en Python consiste principalement en l’algorithme de recherche d’un attribut d’une instance ; celui-ci regarde :

  1. d’abord dans l’instance ;

  2. ensuite dans la classe ;

  3. ensuite dans les super-classes.

Ordre sur les super-classes

Le problème revient donc, pour le dernier point, à définir un ordre sur l’ensemble des super-classes. On parle bien, naturellement, de toutes les super-classes, pas seulement celles dont on hérite directement - en termes savants on dirait qu’on s’intéresse à la fermeture transitive de la relation d’héritage.

L’algorithme utilisé pour cela depuis la version 2.3 est connu sous le nom de linéarisation C3. Cet algorithme n’est pas propre à python, comme vous pourrez le lire dans les références citées dans la dernière section.

Nous ne décrirons pas ici l’algorithme lui-même dans le détail ; par contre nous allons :

Vous trouverez dans les références (voir ci-dessous la dernière section, “Pour en savoir plus”) des liens vers des documents plus techniques si vous souhaitez creuser le sujet.

Les bonnes propriétés attendues

Il y a un certain nombre de bonnes propriétés que l’on attend de cet algorithme.

Priorité au spécifique

Lorsqu’une classe A hérite d’une classe B, on s’attend à ce que les méthodes définies sur A, qui sont en principe plus spécifiques, soient utilisées de préférence à celles définies sur B.

Priorité à gauche

Lorsqu’on utilise l’héritage multiple, on mentionne les classes mères dans un certain ordre, qui n’est pas anodin. Les classes mentionnées en premier sont bien entendu celles desquelles on veut hériter en priorité.

La Method Resolution Order (MRO)

De manière un peu plus formelle

Pour reformuler les deux points ci-dessus, on s’intéresse à la mro d’une classe O, et on veut avoir les deux bonnes propriétés suivantes :

Limitations : toutes les hiérarchies ne peuvent pas être traitées

L’algorithme C3 permet de calculer un ordre sur S\cal{S} qui respecte toutes ces contraintes, lorsqu’il en existe un.

En effet, dans certains cas on ne peut pas trouver un tel ordre, on le verra plus bas, mais dans la pratique, il est assez rare de tomber sur de tels cas pathologiques ; et lorsque cela se produit c’est en général le signe d’erreurs de conception plus profondes.

Un exemple très simple

On se donne la hiérarchie suivante :

class LeftTop:
    def attribut(self): 
        return "attribut(LeftTop)"
    
class LeftMiddle(LeftTop): 
    pass

class Left(LeftMiddle): 
    pass

class Middle: 
    pass

class Right:
    def attribut(self): 
        return "attribut(Right)"

class Class(Left, Middle, Right): 
    pass

instance = Class()

qui donne en version dessinée, avec deux points rouges pour représenter les deux définitions de la méthode attribut :

Les deux règles, telles que nous les avons énoncées en premier lieu (priorité à gauche, priorité au spécifique) sont un peu contradictoires ici. En fait, c’est la méthode de LeftTop qui est héritée dans Class, comme on le voit ici :

instance.attribut() == 'attribut(LeftTop)'
True

Exercice : Remarquez qu’ici Right a elle-même un héritage très simple. À titre d’exercice, modifiez le code ci-dessus pour faire que Right hérite de la classe LeftMiddle ; de quelle classe d’après vous est-ce que Class hérite attribut dans cette configuration ?

Si cela ne vous convient pas

C’est une évidence, mais cela va peut-être mieux en le rappelant : si la méthode que vous obtenez “gratuitement” avec l’héritage n’est pas celle qui vous convient, vous avez naturellement toujours la possibilité de la redéfinir, et ainsi d’en choisir une autre. Dans notre exemple si on préfère la méthode implémentée dans Right, on définira plutôt la classe Class comme ceci :

class Class(Left, Middle, Right):
    # en redéfinissant explicitement la méthode
    # attribut ici on court-circuite la mro
    # et on peut appeler explicitement une autre
    # version de attribut()
    def attribut(*args, **kwds):
        return Right.attribut(*args, **kwds)
    
instance2 = Class()
instance2.attribut()
'attribut(Right)'

Ou encore bien entendu, si dans votre contexte vous devez appelez les deux méthodes dont vous pourriez hériter et les combiner, vous pouvez le faire aussi, par exemple comme ceci :

class Class(Left, Middle, Right):
    # pour faire un composite des deux méthodes
    # trouvées dans les classes mères
    def attribut(*args, **kwds):
        return (  LeftTop.attribut(*args, **kwds) 
                + " ** " 
                + Right.attribut(*args, **kwds))
    
instance3 = Class()
instance3.attribut()
'attribut(LeftTop) ** attribut(Right)'

Un exemple un peu plus compliqué

Voici un exemple, assez parlant, tiré de la deuxième référence (voir ci-dessous la dernière section, “Pour en savoir plus”).

O = object
class F(O): pass
class E(O): pass
class D(O): pass
class C(D, F): pass
class B(E, D): pass
class A(B, C): pass

Cette hiérarchie nous donne, en partant de A, l’ordre suivant :

                               6
                              ---
    Level 3                  | O |
                           /  ---  \
                          /    |    \
                         /     |     \
                        /      |      \
                      ---     ---    ---
    Level 2        2 | E | 4 | D |  | F | 5
                      ---     ---    ---
                       \      / \     /
                        \    /   \   /
                         \  /     \ /
                          ---     ---
    Level 1            1 | B |   | C | 3
                          ---     ---
                           \       /
                            \     /
                              ---
    Level 0                0 | A |
                              ---

Que l’on peut calculer, sous l’interpréteur python, avec la méthode mro sur la classe de départ :

A.mro()
[__main__.A, __main__.B, __main__.E, __main__.C, __main__.D, __main__.F, object]

Un exemple qui ne peut pas être traité

Voici enfin un exemple de hiérarchie pour laquelle on ne peut pas trouver d’ordre qui respecte les bonnes propriétés que l’on a vues tout à l’heure, et qui pour cette raison sera rejetée par l’interpréteur python. D’abord en version dessinée :

# puis en version code
class X: pass
class Y: pass
class XY(X, Y): pass
class YX(Y, X): pass

# on essaie de créer une sous-classe de XY et YX
try:
    class Class(XY, YX): pass 
# mais ce n'est pas possible
except Exception as e:
    print(f"OOPS, {type(e)}, {e}")
OOPS, <class 'TypeError'>, Cannot create a consistent method resolution
order (MRO) for bases X, Y

Pour en savoir plus

  1. Un blog de Guido Van Rossum qui retrace l’historique des différents essais qui ont été faits avant de converger sur le modèle actuel.

  2. Un article technique qui décrit le fonctionnement de l’algorithme de calcul de la MRO, et donne des exemples.

  3. L’article de Wikipedia sur l’algorithme C3.