En Python, la creación de un objeto de clase es un proceso de dos etapas:
__new__, que se encarga de asignar y devolver una nueva instancia de la clase.__init__, que inicializa el objeto ya creado.__new__ — creación (se puede devolver cualquier nuevo objeto; parámetro obligatorio — clase).__init__ — inicialización (trabaja con self, una instancia ya asignada).__new__ se sobreescribe para herederos de tipos inmutables (tuple, str, int), creación de singletons y el patrón "fábrica".class MyStr(str): def __new__(cls, value): print("__new__ llamado") instance = super().__new__(cls, value.upper()) # se cambia el valor antes de la creación return instance def __init__(self, value): print("__init__ llamado", value) s = MyStr('abc') # __new__ llamado -> __init__ llamado abc print(s) # 'ABC'
¿Se puede inicializar un objeto inmutable a través de __init__, si la clase no tiene implementado __new__?
¡No! Por ejemplo, para tipos inmutables (como str, int, tuple) cualquier modificación a los campos/valores debe hacerse en __new__, de lo contrario, no se puede cambiar el valor en __init__.
class MyTuple(tuple): def __init__(self, items): print(f'__init__! {items}') def __new__(cls, items): print(f'__new__! {items}') return super().__new__(cls, map(str, items)) t = MyTuple([1, 2, 3]) print(t) # ('1', '2', '3')
Historia
En un gran proyecto en Django, implementaron la herencia de str para almacenar tipos de cadenas especiales. Intentaron modificar el valor en __init__ — pero el resultado no cambiaba, ya que str es inmutable. Lo corrigieron solo después de estudiar y sobreescribir __new__.
Historia
Al implementar el patrón Singleton, olvidaron implementar la lógica para devolver la instancia existente en __new__: varias llamadas a la clase seguían creando nuevos objetos, lo que llevaba a la fragmentación de memoria.
Historia
En una biblioteca para la serialización de datos, al almacenar en caché utilizaron una clase con __init__ sobreescrito, pero no se dieron cuenta de que para la caché era necesario devolver el mismo objeto a través de __new__. Como resultado, la caché se rompía en cada nueva llamada, porque se creaban diferentes objetos.