Instanciation de Python

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; /* Ugly exception: when the call was type(something), don't call tp_init on the result. */ if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; /* If the returned object is not an instance of type, it won't be initialized. */ 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) # invoke the __new__ of the type if not we_are_jitted(): # note that the annotator will figure out that self.w_new_function # can only be None if the newshortcut config option is not set w_newfunc = self.w_new_function else: # for the JIT it is better to take the slow path because normal lookup # is nicely optimized, but the self.w_new_function attribute is not # known to the JIT w_newfunc = None if w_newfunc is None: w_newtype, w_newdescr = self.lookup_where('__new__') if w_newdescr is None: # see test_crash_mro_without_object_1 raise oefmt(space.w_TypeError, "cannot create '%N' instances", self) w_newfunc = space.get(w_newdescr, self) if (space.config.objspace.std.newshortcut and not we_are_jitted() and isinstance(w_newtype, W_TypeObject)): self.w_new_function = w_newfunc w_newobject = space.call_obj_args(w_newfunc, self, __args__) call_init = space.isinstance_w(w_newobject, self) # maybe invoke the __init__ of the type if (call_init and not (space.is_w(self, space.w_type) and not __args__.keywords and len(__args__.arguments_w) == 1)): w_descr = space.lookup(w_newobject, '__init__') if w_descr is not None: # see test_crash_mro_without_object_2 w_result = space.get_and_call_args(w_descr, w_newobject, __args__) if not space.is_w(w_result, space.w_None): raise oefmt(space.w_TypeError, "__init__() should return None") return w_newobject 



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:


  1. Foo (* args, ** kwargs) est équivalent à Foo .__ appelez __ (* args, ** kwargs) .
  2. É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) .
  3. type .__ call __ (Foo, * args, ** kwargs) appelle la méthode type .__ new __ (Foo, * args, ** kwargs) , qui renvoie obj .
  4. obj est initialisé en appelant obj .__ init __ (* args, ** kwargs) .
  5. 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é .

Source: https://habr.com/ru/post/fr480022/


All Articles