Herencia en Python
Aprende herencia en Python: subclases, sobreescritura de métodos, super(), herencia múltiple, MRO e isinstance — con ejemplos ejecutables.
Comprender la herencia en Python
La herencia te permite construir una nueva clase sobre una existente. La nueva clase (la subclase o clase hija) obtiene automáticamente todos los atributos y métodos de la clase existente (la superclase, clase base o clase padre). Luego puedes añadir comportamiento adicional o sobrescribir selectivamente lo heredado.
Este capítulo abarca:
- La sintaxis para crear subclases
- Cómo funciona
super()y por qué deberías usarlo - Sobreescritura de métodos y llamada a la versión del padre
- Herencia simple, multinivel y múltiple
- El Orden de Resolución de Métodos (MRO) de Python
isinstance()eissubclass()para verificaciones de tipo en tiempo de ejecución- Errores comunes
Antes de leer este capítulo, asegúrate de estar familiarizado con las clases y objetos en Python. Para otros pilares de POO relacionados, consulta Encapsulación en Python y Clases abstractas en Python.
La sintaxis de la herencia
Para crear una subclase, escribe el nombre de la clase padre dentro de paréntesis tras el nombre de la nueva clase:
class ParentClass:
pass
class ChildClass(ParentClass):
passUn ejemplo mínimo pero concreto con una clase base Vehicle y una subclase Car:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # delegate to Vehicle.__init__
self.num_doors = num_doors
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry started.
camry.stop() # Toyota Camry stopped.
print(camry.num_doors) # 4Car hereda start() y stop() de Vehicle sin duplicarlos. Añadir num_doors es la única preocupación nueva que Car debe manejar.
Uso de super()
super() devuelve un objeto proxy que delega las llamadas a métodos en la clase padre. Su uso más común es dentro de __init__ para inicializar los atributos del padre antes de añadir los propios de la clase hija:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # runs Vehicle.__init__
self.num_doors = num_doors¿Por qué preferir super() en lugar de escribir Vehicle.__init__(self, ...) directamente?
- Mantenimiento: si renombras o reemplazas la clase padre, solo cambias una línea.
- Herencia múltiple:
super()sigue el Orden de Resolución de Métodos (MRO) de Python, de modo que cada clase en la cadena se llama correctamente. Codificar el nombre del padre directamente omite esa lógica.
Puedes llamar a super() para cualquier método, no solo __init__:
class Car(Vehicle):
def start(self):
super().start() # call Vehicle.start first
print(f"({self.num_doors}-door model ready)")Sobreescritura de métodos
Una subclase sobreescribe un método del padre definiendo un método con el mismo nombre. Python siempre llama primero a la versión más derivada:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def start(self):
print(f"{self.make} {self.model} revved the engine and started.")
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry revved the engine and started.
camry.stop() # Toyota Camry stopped. (inherited, not overridden)Cuando quieres extender en lugar de reemplazar el comportamiento del padre, llama a super() dentro de la sobreescritura:
class Car(Vehicle):
def start(self):
super().start() # Vehicle.start runs first
print("Seatbelt reminder: buckle up!") # then add the extra stepHerencia de atributos de clase
La herencia también se aplica a los atributos de clase (compartidos entre instancias). Una subclase puede sobrescribirlos de la misma manera que sobreescribe métodos:
class Vehicle:
wheels = 4
class Motorcycle(Vehicle):
wheels = 2 # override the class attribute
class Car(Vehicle):
pass # inherits wheels = 4
print(Motorcycle.wheels) # 2
print(Car.wheels) # 4
print(Vehicle.wheels) # 4Tipos de herencia
Herencia simple
Un hijo, un padre — el patrón más común.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Dog(Animal):
def speak(self):
return f"{self.name} says woof!"
dog = Dog("Rex")
print(dog.speak()) # Rex says woof!Herencia multinivel
Una subclase puede ser a su vez subclasificada, creando una cadena:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year)
self.num_doors = num_doors
def start(self):
print(f"{self.make} {self.model} started.")
class ElectricCar(Car):
def __init__(self, make, model, year, range_km):
super().__init__(make, model, year) # calls Car.__init__
self.range_km = range_km
def start(self):
print(f"{self.make} {self.model} powered up silently.")
def charge(self):
print(f"Charging... range is {self.range_km} km.")
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
tesla.start() # Tesla Model 3 powered up silently.
tesla.charge() # Charging... range is 580 km.
tesla.stop() # Tesla Model 3 stopped. (inherited from Vehicle)ElectricCar se sitúa dos niveles por debajo de Vehicle. Cada llamada a super().__init__ sube un nivel en la cadena, de modo que los tres métodos __init__ se ejecutan.
Herencia múltiple
Python permite que una clase herede de más de un padre. Enuméralos entre paréntesis, separados por comas:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Swimmer:
def swim(self):
return f"{self.name} swims."
class Flyer:
def fly(self):
return f"{self.name} flies."
class Duck(Animal, Swimmer, Flyer):
def speak(self):
return f"{self.name} says quack!"
donald = Duck("Donald")
print(donald.speak()) # Donald says quack!
print(donald.swim()) # Donald swims.
print(donald.fly()) # Donald flies.La herencia múltiple es poderosa pero introduce complejidad. Usa mixins — clases pequeñas de propósito único que añaden una sola capacidad — para mantenerla manejable. Swimmer y Flyer anteriores son exactamente ese patrón.
El Orden de Resolución de Métodos (MRO)
Cuando Python busca un método o atributo, sigue un orden de búsqueda determinista llamado Orden de Resolución de Métodos (MRO). Para la herencia simple, el orden es obvio (hijo → padre → abuelo → object). Para la herencia múltiple, Python utiliza el algoritmo de linealización C3 para producir un orden inequívoco.
Inspecciona el MRO de cualquier clase con .__mro__ o help():
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)El MRO importa más cuando varios padres definen el mismo método. Python elige la primera clase del MRO que lo define. Por eso super() es importante en jerarquías de herencia múltiple: cada clase del MRO llama a super(), de modo que todas las clases de la cadena tienen la oportunidad de ejecutarse.
class Base:
def greet(self):
print("Hello from Base")
class Left(Base):
def greet(self):
print("Hello from Left")
super().greet()
class Right(Base):
def greet(self):
print("Hello from Right")
super().greet()
class Child(Left, Right):
def greet(self):
print("Hello from Child")
super().greet()
Child().greet()
# Hello from Child
# Hello from Left
# Hello from Right
# Hello from BaseCada llamada a super() sigue el MRO (Child → Left → Right → Base), de modo que cada greet() se ejecuta exactamente una vez aunque Base aparezca como padre tanto de Left como de Right. Este es el problema del diamante — el MRO de Python lo resuelve de forma limpia.
Verificar la herencia en tiempo de ejecución
isinstance(obj, cls)
Devuelve True si obj es una instancia de cls o cualquiera de sus subclases:
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
print(isinstance(tesla, ElectricCar)) # True
print(isinstance(tesla, Car)) # True — Car is a parent
print(isinstance(tesla, Vehicle)) # True — Vehicle is a grandparent
print(isinstance(tesla, str)) # FalseEsto es más fiable que comparar type(obj) == Car, que devuelve False para subclases.
issubclass(sub, cls)
Devuelve True si sub es una subclase de cls (incluyéndose a sí misma):
print(issubclass(ElectricCar, Vehicle)) # True
print(issubclass(Car, ElectricCar)) # False
print(issubclass(Car, Car)) # True — a class is a subclass of itselfErrores comunes
Olvidar llamar a super().__init__()
Si el __init__ de la clase hija no llama a super().__init__(), los atributos del padre nunca se establecen. Cualquier método que los espere lanzará un AttributeError:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
# forgot super().__init__(...)
self.num_doors = num_doors
c = Car("Toyota", "Camry", 2023)
c.start() # AttributeError: 'Car' object has no attribute 'make'Sobreescribir sin llamar a super() cuando se pretendía extender
Si sobreescribes un método y olvidas super(), la versión del padre nunca se ejecuta. Esto silenciosamente elimina comportamiento del que otro código puede depender.
Jerarquías profundas de herencia múltiple
Más de dos niveles de herencia múltiple es muy difícil de razonar. Si te encuentras escribiendo class Foo(A, B, C, D), considera usar composición — almacenar instancias como atributos — en su lugar.
Beneficios de la herencia
- Reutilización de código — la lógica compartida vive en un único lugar; las subclases la heredan de forma gratuita.
- Extensibilidad — puedes añadir o cambiar comportamiento en una subclase sin tocar el padre, manteniendo sin cambios a los llamadores existentes.
- Polimorfismo — las funciones que aceptan un
Vehiclefuncionan igualmente bien con cualquierCar,MotorcycleoElectricCar. Consulta Polimorfismo en Python para una visión completa.