Disons que vous avez une classe Foo :
class Foo(object): def __init__(self, x, y=0): self.x = x self.y = y
Que se passe-t-il lorsque vous créez son objet?
f = Foo(1, y=2)
Quelle méthode est appelée en premier avec cet appel à Foo ? La plupart des nouveaux arrivants, et peut-être un bon nombre de pythonistes expérimentés, répondront immédiatement: "méthode __init__ ". Mais si vous regardez attentivement les extraits ci-dessus, il deviendra bientôt clair qu'une telle réponse est incorrecte.
__init__ ne renvoie aucun résultat, et Foo (1, y = 2) , au contraire, retourne une instance de la classe . De plus, __init__ prend self comme premier paramètre, ce qui ne se produit pas lors de l'appel de Foo (1, y = 2) . La création d'une instance est un peu plus compliquée, dont nous parlerons dans cet article.
Procédure de création d'objet
L'instanciation de Python comprend plusieurs étapes. La compréhension de chaque étape nous rapproche un peu plus de la compréhension de la langue dans son ensemble. Foo est une classe, mais en Python, les classes sont aussi des objets! Les classes, fonctions, méthodes et instances sont tous des objets, et chaque fois que vous mettez des crochets après leur nom, vous appelez leur méthode __call__ . Donc Foo (1, y = 2) est l'équivalent de Foo .__ appelez __ (1, y = 2) . De plus, la méthode __call__ est déclarée dans la classe de l'objet Foo . Quelle est la classe de l'objet Foo ?
>>> Foo.__class__ <class 'type'>
La classe Foo est donc une instance de la classe type et l'appel de la méthode __call__ de la dernière renvoie la classe Foo . Voyons maintenant ce qu'est la méthode __call__ de la classe type . Voici ses implémentations en C dans CPython et dans PyPy. Si vous en avez assez de les regarder, faites défiler un peu plus loin pour trouver une version simplifiée:
Cpython
Lien vers la source .
static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; if (type->tp_new == NULL) { PyErr_Format(PyExc_TypeError, "cannot create '%.100s' instances", type->tp_name); return NULL; } obj = type->tp_new(type, args, kwds); obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; if (!PyType_IsSubtype(Py_TYPE(obj), type)) return obj; type = Py_TYPE(obj); if (type->tp_init != NULL) { int res = type->tp_init(obj, args, kwds); if (res < 0) { assert(PyErr_Occurred()); Py_DECREF(obj); obj = NULL; } else { assert(!PyErr_Occurred()); } } return obj; }
Pypy
Lien vers la source .
def descr_call(self, space, __args__): promote(self)
Si vous oubliez toutes sortes de contrôles d'erreur, les codes ci-dessus sont à peu près équivalents à ceci:
def __call__(obj_type, *args, **kwargs): obj = obj_type.__new__(*args, **kwargs) if obj is not None and issubclass(obj, obj_type): obj.__init__(*args, **kwargs) return obj
__new__ alloue de la mémoire à un objet "vide" et appelle __init__ pour l'initialiser.
Pour résumer:
- Foo (* args, ** kwargs) est équivalent à Foo .__ appelez __ (* args, ** kwargs) .
- Étant donné que l'objet Foo est une instance de la classe type , appeler Foo .__ appeler __ (* args, ** kwargs) est équivalent à type .__ appeler __ (Foo, * args, ** kwargs) .
- type .__ call __ (Foo, * args, ** kwargs) appelle la méthode type .__ new __ (Foo, * args, ** kwargs) , qui renvoie obj .
- obj est initialisé en appelant obj .__ init __ (* args, ** kwargs) .
- Le résultat de l'ensemble du processus est un obj initialisé.
Personnalisation
Tournons maintenant notre attention vers __nouveau__ . Cette méthode alloue de la mémoire à l'objet et la renvoie. Vous êtes libre de personnaliser ce processus de différentes manières. Il convient de noter que bien que __new__ soit une méthode statique, vous n'avez pas besoin de la déclarer en utilisant @staticmethod : l'interpréteur traite __new__ comme un cas spécial.
Un exemple courant de remplacements __new__ est la création de Singleton:
class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance
Ensuite:
>>> s1 = Singleton() ... s2 = Singleton() ... s1 is s2 True
Notez que __init__ sera appelé à chaque appel de Singleton () , donc il faut faire attention.
Un autre exemple de remplacements __new__ est l'implémentation du modèle Borg («Borg») :
class Borg(object): _dict = None def __new__(cls, *args, **kwargs): obj = super().__new__(cls, *args, **kwargs) if cls._dict is None: cls._dict = obj.__dict__ else: obj.__dict__ = cls._dict return obj
Ensuite:
>>> b1 = Borg() ... b2 = Borg() ... b1 is b2 False >>> b1.x = 8 ... b2.x 8
Gardez à l'esprit que bien que les exemples ci-dessus démontrent les possibilités de remplacer __new__ , cela ne signifie pas qu'il doit être utilisé:
__new__ est l'une des victimes d'abus les plus fréquentes. Ce qui peut être fait en remplaçant cette méthode est le plus souvent mieux réalisé par d'autres moyens. Cependant, quand c'est vraiment nécessaire, __new__ est un outil extrêmement utile et puissant.
- Arion Sprag, Le vieux bien oublié en Python
On rencontre rarement un problème en Python, où la meilleure solution était d'utiliser __new__ . Mais lorsque vous avez un marteau, chaque problème commence à ressembler à un clou, alors préférez toujours utiliser l'outil le plus puissant pour utiliser le plus approprié .