Roles y secuencia de __new__ frante a __init__
En el ciclo de vida de un objeto dentro de Python, estos métodos dunder operan en fases distintas pero concatenadas:
__new__: Actúa como constructor primario. Al ser un método estático, recibe la clase destino mediantecls, reserva el espacio de memoria necesario y retorna la referencia al nuevo objeto. Se ejecuta antes de que la instancia sea accesible.__init__: Funciona como inicializador secundario. Opera sobre el objeto ya retornado por__new__y permite asignar valores a atributos específicos. Nunca debe retornar nada distinto aNone.
El interprete une ambos pasos: lo que devuelve __new__ se pasa automáticamente como primer argumento (self) hacia __init__. Si __new__ omite la devolución o retorna un valor inesperado, la fase de inicialización podría saltarse o fallar.
Reglas de implementación
- Solo las clases de estilo moderno (heredan de
objecto son clases base en Python 3) implementan__new__de forma consistente. __new__exige al menos un parámetrocls, suministrado internamente.- Es obligatorio retornar una instancia. Lo habitual es delegar en la clase padre:
super().__new__(cls). - Si se retorna una instancia de otra clase distinta desde
__new__, Python invocará el__init__correspondiente a esa clase destino, ignorando completamente el inicializador de la clase declarada. - Por defecto, si no se sobrescribe
__new__, la jerarquía de herencia remonta hastaobject.__new__().
Comportamiento sin retorno explícito
class ConstructorBase:
def __new__(cls):
print("Fase de reserva de memoria activada")
def __init__(self):
print("Configuración de atributos iniciada")
ConstructorBase()
# Salida:
# Fase de reserva de memoria activada
Cadenado correcto entre fases
class ConstructorBase:
def __new__(cls):
return super().__new__(cls)
def __init__(self):
print("Configuración de atributos iniciada")
ConstructorBase()
# Salida:
# Configuración de atributos iniciada
Control dinámico del tipo retornado
class EntidadA:
def __str__(self):
return "Instancia de Tipo-A"
class ControladorB:
bandera = True
def __new__(cls):
if cls.bandera:
return super().__new__(EntidadA)
return super().__new__(cls)
def __init__(self):
pass
print(ControladorB())
# Salida:
# Instancia de Tipo-A
Nomenclatura funcional: cls y self
Aunque el lenguaje permite identificadores personalizados, PEP 8 estandariza cls y self para garantizar legibilidad en proyectos colaborativos.
Uso de cls
Se emplea exclusivamente en métodos declarados con el decorador @classmethod. El primer parámetro hace referencia a la clase llamante, no a un objeto particular. Esto facilita operaciones dependientes del estado de la clase misma, como fábricas internas o lectura de constantes compartidas.
class GestorClase:
prefijo = "SYS_"
@classmethod
def obtener_registro(cls, veces=2):
return f"{cls.prefijo}" * veces
print(GestorClase.obtener_registro(3))
# Salida:
# SYS_SYS_SYS_
Uso de self
Corresponde a los métodos de instancia normales. Este primer parámetro apunta al objeto concreto que invoca la función, permitiendo manipular datos encapsulados exclusivos de esa instancia.
Métodos estáticos (@staticmethod)
Carecen de enlace automático a ninguna clase ni objeto. Se comportan como funciones utilitarias alojadas dentro del namespace de una clase por organización temática. No reciben ni cls ni self.
modo_test = False
class ValidadorInterno:
@staticmethod
def verificar_entorno():
return modo_test
def ejecutar_prueba(self):
if self.verificar_entorno():
print("Ejecutando escenario de depuración")
else:
print("Modo producción activo")
instancia = ValidadorInterno()
instancia.ejecutar_prueba()