🐍 Guía de Aula Invertida
vistas

Herencia en Python

Hasta ahora, cada clase que escribiste fue independiente de las demás: si dos clases se parecen, tuviste que repetir el mismo __init__ y los mismos métodos en las dos. La herencia permite definir una clase "base" con lo que varias clases tienen en común, y que otras clases "hereden" todo eso sin volver a escribirlo.

RC
Ing. Roy Carrasco
Facultad de Ingeniería de Sistemas · UAB

Bienvenido

En Encapsulación viste cómo proteger el estado interno de una clase. Ahora, con _, __, @property y todo lo demás ya dominado, es momento de resolver un problema distinto: qué hacer cuando dos o más clases se parecen mucho entre sí, y no querés escribir el mismo código dos veces.

Objetivo del módulo

Al finalizar esta guía vas a saber declarar una clase que hereda de otra (class Hija(Padre):), usar super().__init__() para reutilizar el constructor de la clase base, sobrescribir un método para que una subclase se comporte distinto, extender un método heredado con super().metodo() en vez de reemplazarlo del todo, y usar isinstance()/issubclass() para preguntar por la relación entre clases.

💻 Código en vivo

Todos los bloques con el encabezado Python son de solo lectura (para que los escribas a mano en el editor de abajo). El panel editor-panel que sigue a cada uno es donde escribes y ejecutas tu propio código —usa Pyodide, un Python real compilado a WebAssembly. La primera ejecución tarda unos segundos porque descarga el intérprete.

Además del código, esta guía tiene 3 preguntas conceptuales cortas para chequear que entendiste bien el porqué de cada herramienta, no solo su sintaxis.

0 / 0
preguntas y ejercicios enviados

📖 Aprende

Por qué repetir código entre clases parecidas es un problema, y cómo class Hija(Padre): lo resuelve.

💻 Practica

Ejemplos editables y ejecutables en cada sección, construyendo la misma jerarquía de vehículos paso a paso.

🎯 Domina

super(), sobrescritura de métodos, y isinstance()/issubclass().

El problema: código repetido

Imagina dos clases parecidas: Perro y Gato. Ambas guardan un nombre y una edad, y ambas tienen un método para describirse. Sin herencia, ese código se escribe dos veces —casi idéntico.

Python
class Perro:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def describir(self):
        print(self.nombre + " tiene " + str(self.edad) + " años")

    def hacer_sonido(self):
        print("Guau")

class Gato:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def describir(self):
        print(self.nombre + " tiene " + str(self.edad) + " años")

    def hacer_sonido(self):
        print("Miau")

perro = Perro("Rex", 3)
gato = Gato("Michi", 2)
perro.describir()
gato.describir()
✍️ Tu turno: escribe el código y ejecútalo

Salida:

Consola
Rex tiene 3 años
Michi tiene 2 años
__init__ y describir() están duplicados

Fíjate que __init__ y describir() son exactamente iguales en las dos clases —solo hacer_sonido() cambia de verdad. Si mañana agregás un tercer atributo (por ejemplo, raza) a todos los animales, hay que tocar cada clase por separado, y es fácil olvidarse de alguna o escribirla distinto por error. El resto de esta guía muestra cómo evitar esa duplicación con herencia.

Sintaxis básica de herencia

Una clase base (o "padre") reúne lo que varias clases tienen en común. Una clase que hereda de ella (o "hija", o "subclase") se declara con class Hija(Padre):, y automáticamente obtiene todos sus atributos y métodos, sin escribir nada más.

Python
class Animal:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def describir(self):
        print(self.nombre + " tiene " + str(self.edad) + " años")

class Perro(Animal):
    pass

perro = Perro("Rex", 3)
perro.describir()
✍️ Tu turno: escribe el código y ejecútalo

Salida:

Consola
Rex tiene 3 años
pass no significa "vacía"

class Perro(Animal): pass no define ningún atributo ni método propio —pass es solo un relleno sintáctico porque Python exige al menos una línea dentro del cuerpo de la clase. Aun así, Perro ya tiene __init__ y describir(), heredados enteros de Animal. Eso es lo que hace la herencia: nada que escribir, todo heredado.

super().__init__(): reutilizar el constructor

Una subclase casi siempre necesita agregar algo propio —no solo heredar todo tal cual. super() da acceso a la clase base desde adentro de la subclase, y super().__init__(...) llama al constructor del padre para no repetir su lógica.

Python
class Animal:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def describir(self):
        print(self.nombre + " tiene " + str(self.edad) + " años")

class Perro(Animal):
    def __init__(self, nombre, edad, raza):
        super().__init__(nombre, edad)
        self.raza = raza

perro = Perro("Rex", 3, "Labrador")
perro.describir()
print(perro.raza)
✍️ Tu turno: escribe el código y ejecútalo

Salida:

Consola
Rex tiene 3 años
Labrador
super() apunta a la clase de arriba, no a self

super().__init__(nombre, edad) ejecuta el __init__ de Animal, que es quien realmente asigna self.nombre y self.edad. Perro.__init__ no repite esas dos líneas: solo agrega lo que le falta, self.raza. Si Animal cambiara mañana (por ejemplo, agregando validación al nombre), Perro heredaría ese cambio automáticamente, sin tocar nada acá.

Si no defines __init__ en la subclase, hereda el del padre tal cual

En la sección anterior, Perro(Animal): pass ni siquiera necesitó super() porque no definía su propio __init__ —Python usa directamente el de Animal. super() hace falta recién cuando la subclase define su propio __init__ y quiere reutilizar el del padre en vez de repetirlo entero.

Sobrescritura de métodos

Una subclase puede redefinir un método heredado con el mismo nombre para que se comporte distinto. A esto se lo llama sobrescribir (override): cuando se llama ese método sobre un objeto de la subclase, corre la versión nueva, no la del padre.

Python
class Animal:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def hacer_sonido(self):
        print("Este animal no tiene un sonido definido")

class Perro(Animal):
    def hacer_sonido(self):
        print("Guau")

class Gato(Animal):
    def hacer_sonido(self):
        print("Miau")

perro = Perro("Rex", 3)
gato = Gato("Michi", 2)
perro.hacer_sonido()
gato.hacer_sonido()
✍️ Tu turno: escribe el código y ejecútalo

Salida:

Consola
Guau
Miau
Mismo nombre, mismo llamado, comportamiento distinto

perro.hacer_sonido() y gato.hacer_sonido() se llaman exactamente igual, pero cada uno ejecuta la versión definida en su propia clase, no la de Animal. Python busca el método primero en la clase del objeto, y solo sube a la clase base si no lo encuentra ahí. Esta idea —el mismo llamado comportándose distinto según el objeto— es la base de lo que en una guía futura se va a llamar polimorfismo.

Selección múltiple

Si Perro hereda de Animal y ambas clases definen un método hacer_sonido(), ¿qué versión se ejecuta al llamar perro.hacer_sonido() sobre un objeto Perro?

Extender un método con super()

Sobrescribir no siempre significa reemplazar del todo. Con super().metodo() dentro de la versión nueva, una subclase puede ejecutar primero la lógica del padre y después agregar la suya propia —extender en vez de reemplazar.

Python
class Animal:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def describir(self):
        print(self.nombre + " tiene " + str(self.edad) + " años")

class Perro(Animal):
    def __init__(self, nombre, edad, raza):
        super().__init__(nombre, edad)
        self.raza = raza

    def describir(self):
        super().describir()
        print("Es de raza " + self.raza)

perro = Perro("Rex", 3, "Labrador")
perro.describir()
✍️ Tu turno: escribe el código y ejecútalo

Salida:

Consola
Rex tiene 3 años
Es de raza Labrador
super().describir() no es una llamada recursiva

Dentro de Perro.describir(), super().describir() llama a la versión de Animal, no se llama a sí misma. Si en cambio hubieras escrito self.describir() ahí adentro, sí sería recursión infinita, porque self es un Perro y volvería a entrar a la misma versión sobrescrita una y otra vez.

Selección múltiple

Dentro de Perro.describir(), ¿qué hace exactamente la línea super().describir()?

isinstance() e issubclass()

Con herencia de por medio, un objeto puede pertenecer a más de una clase a la vez —a la suya propia y a todas sus clases base. isinstance(objeto, Clase) pregunta por esa relación, y issubclass(Subclase, Clase) hace la misma pregunta pero entre dos clases.

Python
class Animal:
    pass

class Perro(Animal):
    pass

perro = Perro()
print(isinstance(perro, Perro))
print(isinstance(perro, Animal))
print(isinstance(perro, str))
print(issubclass(Perro, Animal))
print(issubclass(Animal, Perro))
✍️ Tu turno: escribe el código y ejecútalo

Salida:

Consola
True
True
False
True
False
isinstance() considera toda la cadena de herencia

isinstance(perro, Animal) da True aunque perro fue creado como Perro, no como Animal directamente —porque Perro hereda de Animal, todo objeto Perro "también es" un Animal. En cambio, type(perro) == Animal daría False, porque type() compara la clase exacta, sin mirar la herencia. Por eso isinstance() es casi siempre la forma correcta de preguntar "¿esto es de este tipo?" en Python.

Selección múltiple

Con class Perro(Animal): pass y perro = Perro(), ¿por qué isinstance(perro, Animal) da True si perro nunca fue creado como Animal("...") directamente?

Errores comunes

❌ Olvidar super().__init__()

Si la subclase define su propio __init__ sin llamar a super().__init__(...), los atributos que el padre debía asignar (como nombre o edad) simplemente no existen —cualquier método heredado que los use falla con AttributeError.

❌ Usar self.metodo() en vez de super().metodo()

Dentro de una versión sobrescrita, llamar self.metodo() en vez de super().metodo() vuelve a ejecutar la misma versión sobrescrita —una recursión infinita que termina en RecursionError, no una llamada a la del padre.

❌ Pensar que Python soporta sobrecarga de métodos

A diferencia de Java o C#, Python no permite dos métodos con el mismo nombre y distinta cantidad de parámetros dentro de la misma clase: la segunda definición simplemente reemplaza a la primera. No hay "sobrecarga" (overload), solo sobrescritura (override) entre clase base y subclase.

❌ Confundir type(x) == Clase con isinstance(x, Clase)

type(perro) == Animal exige que la clase sea exactamente Animal, y da False para un Perro. isinstance(perro, Animal) considera la herencia y da True. Casi siempre isinstance() es la forma correcta de preguntar por el tipo de un objeto.

Ejercicios Propuestos

Pon en práctica lo aprendido resolviendo los siguientes ejercicios directamente en el bloque editable.

  1. Escribe una clase Persona con __init__(self, nombre, edad) y un método presentarse(self) que imprima "Hola, soy " + self.nombre. Escribe una clase Estudiante(Persona) que agregue carrera en su propio __init__ (usando super().__init__() para nombre y edad), y crea un objeto para probarla.
  2. Escribe una clase Figura con un método area(self) que imprima "Área no definida". Escribe Rectangulo(Figura) y Circulo(Figura), cada una con su propio __init__ (con los datos que necesite) y su propio area(self) sobrescrito, que calcule e imprima el área real.
  3. Escribe una clase Empleado con __init__(self, nombre, salario_base) y un método calcular_salario(self) que devuelva self.salario_base. Escribe Gerente(Empleado) que agregue bono en su __init__ (con super().__init__()), y sobrescriba calcular_salario(self) para devolver super().calcular_salario() + self.bono.
  4. Escribe una clase CuentaBancaria con __init__(self, saldo) y un método aplicar_interes(self) que sume un 1% al saldo. Escribe CuentaAhorro(CuentaBancaria) que sobrescriba aplicar_interes(self) llamando a super().aplicar_interes() y sumando además un 2% extra —dos interés distintos combinados con super().
  5. Escribe isinstance()/issubclass() de práctica: crea objetos de Estudiante (ejercicio 1) y de Persona directa, e imprime seis resultados: si cada objeto es instancia de Persona, de Estudiante, y si Estudiante es subclase de Persona.
✍️ Resuelve aquí los ejercicios

Reto Final

Completa una pequeña jerarquía de empleados que combine todo lo visto: herencia, super(), sobrescritura de métodos e isinstance().

  • Empleado: __init__(self, nombre, salario_base) guarda ambos datos. Método calcular_salario(self) devuelve self.salario_base.
  • Vendedor(Empleado): __init__(self, nombre, salario_base, comision) llama super().__init__(nombre, salario_base) y guarda self.comision. Sobrescribe calcular_salario(self) para devolver super().calcular_salario() + self.comision.
  • Gerente(Empleado): __init__(self, nombre, salario_base, bono) llama super().__init__(nombre, salario_base) y guarda self.bono. Sobrescribe calcular_salario(self) para devolver super().calcular_salario() + self.bono.
  • Una función mostrar_nomina(lista_empleados) que recorra la lista con un for e imprima, para cada uno, nombre + ": " + str(calcular_salario()) —el mismo llamado funcionando para los tres tipos de objeto, sin preguntar de qué clase es cada uno.

Pruébala con este código:

Python
empleados = [
    Empleado("Ana", 3000),
    Vendedor("Beto", 2500, 800),
    Gerente("Carla", 4000, 1500)
]
mostrar_nomina(empleados)
print(isinstance(empleados[1], Empleado))
print(isinstance(empleados[0], Vendedor))
✍️ Resuelve el reto acá
Salida esperada
Ana: 3000
Beto: 3300
Carla: 5500
True
False

Fíjate que mostrar_nomina() llama calcular_salario() sin preguntar en ningún momento de qué clase es cada empleado —cada objeto sabe resolver ese llamado a su manera.

Cheat Sheet

UsoEjemplo
Declarar una subclaseclass Hija(Padre): ...
Heredar todo sin cambiosclass Hija(Padre): pass
Reutilizar el constructor del padresuper().__init__(...)
Sobrescribir un métodoRedefinirlo con el mismo nombre en la subclase
Extender un método heredadosuper().metodo() dentro de la versión nueva
¿Es esta instancia de esta clase (o de una base)?isinstance(objeto, Clase)
¿Esta clase hereda de esta otra?issubclass(Subclase, Clase)
Clase exacta de un objeto (sin herencia)type(objeto)

¿Sabías que...?

🐍 Toda clase en Python hereda de object

Aunque nunca lo escribas, class Animal: es en realidad class Animal(object): por debajo. Métodos como __str__ o __eq__ que quizás uses más adelante ya existen en object —cuando los definís en tu propia clase, en el fondo también los estás sobrescribiendo.

🧬 Python sí permite herencia múltiple

A diferencia de Java o C#, en Python una clase puede heredar de más de una a la vez: class Hija(Padre1, Padre2):. Cuando dos padres definen el mismo método, Python decide cuál usar con un orden llamado MRO (Method Resolution Order) —un tema avanzado que no hace falta dominar todavía, pero que existe.

🧩 El name mangling de Encapsulación pensaba en esto

En Encapsulación viste que self.__atributo se renombra por dentro a _Clase__atributo. Ahora que ya sabés herencia, tiene más sentido por qué: evita que un __atributo de una clase base choque por accidente con otro del mismo nombre definido en una subclase que la hereda.