Instanciación de Python

Digamos que tienes una clase Foo :


class Foo(object): def __init__(self, x, y=0): self.x = x self.y = y 

¿Qué sucede cuando creas su objeto?


 f = Foo(1, y=2) 

¿Qué método se llama primero con esta llamada a Foo ? La mayoría de los recién llegados, y quizás algunos pitonistas experimentados, responderán de inmediato: "método __init__ ". Pero si observa de cerca los fragmentos anteriores, pronto quedará claro que dicha respuesta es incorrecta.


__init__ no devuelve ningún resultado, y Foo (1, y = 2) , por el contrario, devuelve una instancia de la clase . Además, __init__ toma self como primer parámetro, lo que no sucede cuando se llama a Foo (1, y = 2) . Crear una instancia es un poco más complicado, del que hablaremos en este artículo.


Procedimiento de creación de objetos


La instanciación de Python consta de varias etapas. Comprender cada paso nos acerca un poco más a la comprensión del lenguaje en su conjunto. ¡Foo es una clase, pero en Python, las clases también son objetos! Las clases, funciones, métodos e instancias son todos objetos, y cada vez que pone corchetes después de su nombre, llama a su método __call__ . Entonces Foo (1, y = 2) es el equivalente de Foo .__ call __ (1, y = 2) . Además, el método __call__ se declara en la clase del objeto Foo . ¿Cuál es la clase del objeto Foo ?


 >>> Foo.__class__ <class 'type'> 

Por lo tanto, la clase Foo es una instancia de la clase type y llamar al método __call__ de la última devuelve la clase Foo . Ahora veamos cuál es el método __call__ de la clase type . A continuación se muestran sus implementaciones en C en CPython y en PyPy. Si te cansas de verlos, desplázate un poco más para encontrar una versión simplificada:


Cpython


Enlace a la fuente .


 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


Enlace a la fuente .


 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 olvida todo tipo de comprobaciones de errores, los códigos anteriores son aproximadamente equivalentes a esto:


 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__ asigna memoria para un objeto "vacío" y llama a __init__ para inicializarlo.


Para resumir:


  1. Foo (* args, ** kwargs) es equivalente a Foo .__ call __ (* args, ** kwargs) .
  2. Dado que el objeto Foo es una instancia de la clase type , llamar a Foo .__ call __ (* args, ** kwargs) es equivalente a type .__ call __ (Foo, * args, ** kwargs) .
  3. type .__ call __ (Foo, * args, ** kwargs) llama al método type .__ new __ (Foo, * args, ** kwargs) , que devuelve obj .
  4. obj se inicializa llamando a obj .__ init __ (* args, ** kwargs) .
  5. El resultado de todo el proceso es un obj inicializado.

Personalización


Ahora volvamos nuestra atención a __nuevo__ . Este método asigna memoria para el objeto y lo devuelve. Puede personalizar este proceso de muchas maneras diferentes. Cabe señalar que, aunque __new__ es un método estático, no necesita declararlo utilizando @staticmethod : el intérprete trata __new__ como un caso especial.


Un ejemplo común de anulaciones de __new__ es crear 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 

Entonces:


 >>> s1 = Singleton() ... s2 = Singleton() ... s1 is s2 True 

Tenga en cuenta que __init__ se llamará cada vez que se llame Singleton () , por lo que se debe tener cuidado.


Otro ejemplo de anulaciones de __new__ es la implementación del patrón 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 

Entonces:


 >>> b1 = Borg() ... b2 = Borg() ... b1 is b2 False >>> b1.x = 8 ... b2.x 8 

Tenga en cuenta que aunque los ejemplos anteriores demuestran las posibilidades de anular __nuevo__ , esto no significa que deba usarse:


__nuevo__ es una de las víctimas de abuso más frecuentes. Lo que se puede hacer anulando este método se logra con mayor frecuencia por otros medios. Sin embargo, cuando es realmente necesario, __new__ es una herramienta extremadamente útil y poderosa.


- Arion Sprag, el viejo bien olvidado en Python


Raramente se encuentra un problema en Python, donde la mejor solución era usar __new__ . Pero cuando tienes un martillo, cada problema comienza a parecerse a un clavo, por lo que siempre prefieres usar la herramienta más poderosa para usar la más adecuada .

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


All Articles