W3docs

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() e issubclass() 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):
    pass

Un 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)  # 4

Car 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 step

Herencia 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)     # 4

Tipos 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 Base

Cada 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))          # False

Esto 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 itself

Errores 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

  1. Reutilización de código — la lógica compartida vive en un único lugar; las subclases la heredan de forma gratuita.
  2. Extensibilidad — puedes añadir o cambiar comportamiento en una subclase sin tocar el padre, manteniendo sin cambios a los llamadores existentes.
  3. Polimorfismo — las funciones que aceptan un Vehicle funcionan igualmente bien con cualquier Car, Motorcycle o ElectricCar. Consulta Polimorfismo en Python para una visión completa.

Práctica

Práctica
Which of the following statements about Python inheritance are correct according to the information provided on the specified URL?
Which of the following statements about Python inheritance are correct according to the information provided on the specified URL?
Was this page helpful?