Skip to article frontmatterSkip to article content

on a vu dans ce chapitre, et spécifiquement ici les mécanismes généraux de l’héritage en Python
mais ici encore, c’est la version simple des choses, car en bon langae objet qui se respecte,
Python nous permet aussi .. de modifier les règles de base de l’héritage (entre autres) !
c’est le propos des métaclasses, que nous allons étudier maintenant

à quoi ça sert ?

hum, bonne question ! en réalité on peut s’en servir pour énormément de choses! (même si, bien souvent il y a d’autres alternatives, dans la ménagerie de concepts Python, pour faire ces choses sans passer par les métaclasses...)
mais bon en tous cas c’est intéressant du point de vue conceptuel !

un prétexte: le singleton

pour quand même travailler sur une application concrète, nous allons illustrer le concept en implémentant la notion de singleton

c’est quoi ? on parle de singleton lorsqu’on veut qu’une classe n’existe qu’en un seul exemplaire dans l’application; comme un espèce d’objet global mais qui serait proprement partagé
par exemple: la configuration; ou une classe API pour dialoguer avec un autre programme; ou bien un cache pour ranger des données...

mais avant d’en arriver là nous allons devoir faire un peu de théorie...

type et object

jusqu’ici nous avons vu que

relation entre objets et types

du coup on peut s’intéresser à la relation binaire entre les objets:

# et pour ça on va se créer quelques classes et objets

class Class: pass

class SubClass(Class): pass

instance = SubClass()

pour introspecter la relation “est une instance de”, c’est facile, on utilise type() !

# remontons la relation en partant de vector

type(instance), type(SubClass), type(Class), type(type)
(__main__.SubClass, type, type, type)

ce qui nous donne un premier élément de réponse:

ce qui nous donne le diagramme suivant

héritage entre classes

maintenant regardons quelle est la relation entre classes et super-classes:

la question ne se pose pas à propos de vector (seules les classes ont des super-classes)
mais pour les autres on obtient ceci - en utilisant l’attribut spécial __bases__

# the base classes of our classes

SubClass.__bases__, Class.__bases__
((__main__.Class,), (object,))
# the base classes of the builtin classes

object.__bases__, type.__bases__
((), (object,))

donc on obtient, si on superpose les deux relations:

comment les objets sont-ils créés ?

une fois que ceci est bien clair, voyons un peu plus en détail comment les objets sont créés en Python
c’est-à-dire quand j’appelle par exemple Class(0):

# the actual code is in C, but a pseudo implementation
# of the type class in Python could look like this

class type:
    # when calling Class(0), we will call this method with
    # cls = Class
    # args = (0,)
    # kwargs = {}
    def __call__(cls, *args, **kwarg):

        # ... a few things could possibly be done to cls here... maybe... or maybe not...

        # then we call cls.__new__() to get a new object
        # think of this as a class method (the object has not been created yet...)
        obj = cls.__new__(cls, *args, **kwargs)

        # ... a few things done to obj here... maybe... or not...

        # then we call obj.__init__()
        obj.__init__(*args, **kwargs)

        # ... maybe a few more things done to obj here

        # then we return obj
        return obj

c’est-à-dire en français, on crée un objet en passant par deux étapes, qui sont chacune liée à une dunder:

le cas d’un objet usuel

on peut donc poursuivre notre analyse, on est toujours en train d’évaluer Class(0)

au moment d’appeler __new__ on la cherche à partir de l’objet Class
qui en général n’a pas redéfini cette méthode
on

pour en savoir plus

cette section de la doc officielle donne tous les détails

https://docs.python.org/3/reference/datamodel.html#customizing-class-creation