Estructura tu código con clases bien diseñadas: inicialización, atributos de instancia o de clase, herencia y sus compromisos.
Abrir esta lección en KodokonTrabajas con objetos de Python todo el tiempo: cadenas, listas, archivos. Escribir tus propias clases significa reunir el estado (los atributos) y el comportamiento (los métodos) detrás de una interfaz coherente. Un punto de vocabulario que a menudo se malinterpreta: __init__ no es un constructor. El objeto ya existe (creado por __new__) cuando __init__ recibe self; su trabajo es inicializar la instancia. Y self no tiene nada de mágico: es el primer parámetro de todo método de instancia, que Python pasa automáticamente en el momento de la llamada.
class Account:
def __init__(self, owner, balance=0.0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("amount must be > 0")
self.balance += amount
return self.balance
def __repr__(self):
return f"Account({self.owner!r}, {self.balance})"
account = Account("Ada")
account.deposit(120.0)
print(account)Una distinción crucial en producción: los atributos de clase se declaran en el cuerpo de la clase y los comparten todas las instancias; los atributos de instancia se crean en self, normalmente en __init__. Al leer, Python busca primero en la instancia, luego recurre a la clase y después a las clases padre. Una asignación self.x = ... siempre crea un atributo de instancia, que oculta el atributo de clase del mismo nombre. La trampa clásica del mundo real: un atributo de clase mutable.
class Basket:
items = []
def add(self, item):
self.items.append(item)
a = Basket()
b = Basket()
a.add("apple")
print(b.items) # ['apple']: shared list!La herencia expresa una relación "es un": un Car es un Vehicle. La subclase hereda los métodos, puede sobrescribirlos, y super() le permite llamar a la versión del padre - indispensable en __init__ para no cortocircuitar la inicialización del padre. Python resuelve las llamadas siguiendo el MRO (Method Resolution Order, el orden de resolución de métodos). No recurras a la herencia de forma sistemática: para reutilizar código sin una relación "es un", prefiere la composición (un objeto posee a otro). Las jerarquías profundas son frágiles: cada clase padre se convierte en una API implícita para todos sus hijos.
class Vehicle:
def __init__(self, brand):
self.brand = brand
def describe(self):
return self.brand
class Car(Vehicle):
def __init__(self, brand, doors):
super().__init__(brand)
self.doors = doors
def describe(self):
base = super().describe()
return f"{base}, {self.doors} doors"
print(Car("Aster", 5).describe())