Aprende leyendo en orden

El constructor __init__ y el destructor __del__

Una guía práctica de __init__ y __del__ de Python. Cubre los atributos requeridos, los valores por defecto y el método de limpieza llamado al hacer del y al salir del programa, todo con diagramas.

La última vez aprendiste los fundamentos de las clases e instancias, self y la forma más simple de __init__. Esta vez profundizamos, usando __init__ para asegurar que cada instancia nazca en un estado válido. Cubriremos los argumentos requeridos que fallan ruidosamente cuando los olvidas, los argumentos por defecto para campos opcionales, y el destructor correspondiente __del__.

Qué ocurre sin __init__

Si tu clase no inicializa los atributos correctamente, puedes acabar con instancias a las que les falten esos atributos, y en el momento en que llames a un método que los toque, te encontrarás con un AttributeError.

En el código de abajo, User solo tiene un método set_name, sin inicializador. Llamar a display() antes que set_name explota porque self.name aún no existe.

class User:
    def set_name(self, name):
        self.name = name

    def display(self):
        print(self.name)

user = User()
user.display()             # AttributeError: no hay atributo name
# self.name no existe hasta que llamas a user.set_name("Ana") primero

Confirma que llamar a un método antes de inicializar sus atributos provoca un AttributeError. Ejecútalo y observa cómo ocurre el error.

Editor Python

Ejecutar el código para ver el resultado

Usa __init__ para imponer atributos requeridos

__init__ es el método especial que Python llama automáticamente cuando se crea una instancia. También se le llama constructor. Los nombres envueltos en doble guión bajo son métodos dunder: la convención de Python para los métodos que llama en momentos específicos.

Al declarar __init__(self, name, email) con parámetros requeridos, olvidar pasar cualquiera de los dos detiene el programa con TypeError. Nunca acabas con un User() incompleto, así que todo el código posterior puede confiar en que "name y email están definitivamente establecidos".

Cómo se conecta __init__ a la creación de instancias
User('Ana', 'ana@x.com')construir instanciavacíallamar a__init__useratributos listos
Llamar a User("Ana", "ana@x.com") hace que Python construya una instancia vacía, ejecute __init__, rellene los atributos y devuelva la instancia terminada. Olvida un argumento y __init__ mismo lanza TypeError.
class User:
    def __init__(self, name, email):    # parámetros requeridos
        print("__init__ llamado")
        self.name = name
        self.email = email

    def display(self):
        print(f"{self.name} <{self.email}>")

user = User("Ana", "ana@example.com")  # establece user.name a "Ana" y user.email a "ana@example.com"
# __init__ llamado
user.display()
# Ana <ana@example.com>

# Olvidar un argumento provoca TypeError
# User()  # → TypeError: faltan 2 argumentos posicionales requeridos

Escribe __init__ con dos parámetros requeridos y prueba tanto la creación, la impresión, como lo que ocurre cuando olvidas uno.

① Define class User:. Dentro de __init__(self, name, email), escribe self.name = name y self.email = email.

② Define def display(self): que imprime f"{self.name} <{self.email}>".

③ Construye ana = User("Ana", "ana@example.com") y llama a ana.display(). Confirma que imprime Ana <ana@example.com>.

④ Luego prueba carlos = User("Carlos")olvidando el email— y confirma que se detiene con TypeError (envuélvelo en try / except para inspeccionar el mensaje).

(Si lo ejecutas correctamente, aparecerá una explicación.)

Editor Python

Ejecutar el código para ver el resultado

Argumentos por defecto para campos opcionales

__init__ es una función normal, así que puedes darle argumentos por defecto. Escribe algo como age=0, y quien llama puede omitirlo (y obtener el valor por defecto) o pasar un valor. Esto te permite dividir tu diseño en "atributos requeridos" y "atributos opcionales".

class User:
    def __init__(self, name, email, age=0):   # age es opcional
        self.name = name
        self.email = email
        self.age = age

ana = User("Ana", "ana@example.com")            # age omitido → 0
carlos = User("Carlos", "carlos@example.com", 30)              # age proporcionado

print(ana.age)   # 0
print(carlos.age)     # 30

No pongas list / dict directamente en un argumento por defecto

Escribir un objeto mutable como valor por defecto —como def __init__(self, tags=[])— cae en una trampa famosa: todas las instancias acaban compartiendo la misma lista. Lo cubrimos en detalle en Argumentos de función y mutabilidad. Es la misma trampa dentro de __init__, así que la solución estándar es tags=None más if tags is None: tags = [] dentro de la función.

Escribe un argumento por defecto como una lista vacía directamente y observa qué sale mal.

① Define class User:. Dentro de __init__(self, name, tags=[]), escribe self.name = name y self.tags = tags (poniendo intencionalmente tags=[] directamente).

② Construye ana = User("Ana") y carlos = User("Carlos").

③ Ejecuta ana.tags.append("vip"), luego print(ana.tags) y print(carlos.tags): confirma que carlos también acaba con "vip".

④ Finalmente, print(ana.tags is carlos.tags) para confirmar que comparten la misma lista.

Editor Python

Ejecutar el código para ver el resultado

__del__ — Limpieza cuando una instancia desaparece

El método dunder emparejado con __init__ es __del__ (el destructor). Mientras __init__ se ejecuta cuando se crea una instancia, __del__ se ejecuta en el momento en que se destruye una instancia, llamado automáticamente por Python.

Hay dos formas principales en las que ocurre la destrucción:

- La eliminas explícitamente con del nombre_de_variable

- La memoria se libera cuando el programa termina (cualquier instancia restante se destruye a su vez)

Debido al segundo caso, __del__ se ejecuta al salir del programa incluso si no lo desencadenas tú mismo.

Cuándo se ejecutan __init__ y __del__
llamar a User('Ana')__init__se disparauservivodel user/ sale el programa__del__se disparamemorialiberadacrearlistodestruirlimpieza
La fila superior es el flujo de __init__: se ejecuta cuando nace la instancia, llamada automáticamente por Python. La fila inferior es el flujo de __del__: se ejecuta cuando muere la instancia, ya sea por del o por salida del programa.

__del__ no se ejecuta en la ejecución del navegador

La ejecución del lado del navegador de este sitio se construye sobre MicroPython, que por diseño no llama a __del__. Las muestras de código de esta sección son para leer y entender el comportamiento. Ejecútalas en CPython en tu terminal local si quieres ver realmente cómo se dispara __del__.

class User:
    def __init__(self, name):
        print(f"creado {name}")
        self.name = name

    def __del__(self):
        print(f"destruido {self.name}")

ana = User("Ana")      # creado Ana
carlos = User("Carlos")          # creado Carlos

del ana                  # destruido Ana  ← se dispara explícitamente aquí
print("a punto de salir")
# a punto de salir
# destruido Carlos  ← se dispara automáticamente al salir del programa

Un patrón común de limpieza — Eliminarte de una lista

Un uso típico de __del__ es limpieza emparejada con la creación: como "eliminarme de una lista que la clase mantiene". En el ejemplo de abajo, la clase User añade el nombre a la variable de clase users al crearse y lo elimina al destruirse.

class User:
    users = []                         # variable de clase: usuarios actualmente vivos

    def __init__(self, name):
        self.name = name
        User.users.append(name)        # registrar al crear

    def __del__(self):
        User.users.remove(self.name)   # eliminar al destruir

ana = User("Ana")
carlos = User("Carlos")
print(User.users)    # ['Ana', 'Carlos']

del ana
print(User.users)    # ['Carlos']

El uso directo de __del__ es raro en la práctica

Cuándo se ejecuta __del__ depende del recolector de basura de Python, así que no puedes predecir el momento de forma fiable. Para una liberación determinista de archivos o conexiones de red, usa sentencias with (gestores de contexto) en lugar de __del__.

Esta vez cubrimos el uso práctico de __init__imponer atributos requeridos y argumentos por defecto— y conocimos el correspondiente __del__ con su patrón típico. Ahora puedes controlar "el ciclo de vida de una instancia desde la creación hasta la destrucción" desde el lado de Python.

A continuación profundizaremos en la verdad detrás de self.x = ... que has estado escribiendo sin pensar mucho. Aclararemos los dos tipos de variables en una clase —variables de clase y variables de instancia— y recorreremos lo que realmente se está creando cuando escribes self.x = ..., observando las referencias con id().

QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Si una clase C tiene __init__(self, name), ¿qué ocurre cuando llamas a C() sin argumentos?

Pregunta 2¿Cuál es el momento correcto en el que se ejecuta __del__?

Pregunta 3¿Por qué se considera problemático escribir def __init__(self, tags=[])?