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; 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
Enlace a la fuente .
def descr_call(self, space, __args__): promote(self)
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:
- Foo (* args, ** kwargs) es equivalente a Foo .__ call __ (* args, ** kwargs) .
- 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) .
- type .__ call __ (Foo, * args, ** kwargs) llama al método type .__ new __ (Foo, * args, ** kwargs) , que devuelve obj .
- obj se inicializa llamando a obj .__ init __ (* args, ** kwargs) .
- 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 .