Saltar al contenido principal

Herencia Python: Buenas prácticas para el código reutilizable

La herencia en Python te permite construir nuevas clases reutilizando y ampliando la funcionalidad de las existentes. Aprende a diseñar relaciones entre clases padre-hijo, a implementar patrones de herencia y a aplicar técnicas como la sustitución de métodos.
Actualizado 12 feb 2025  · 9 min de lectura

Imagina que estás construyendo un sistema de software con múltiples roles de usuario, como estudiantes, profesores y administradores. Estos roles comparten atributos comunes como el nombre y el ID, pero también requieren funcionalidades que les son propias. En lugar de duplicar código, la herencia te permite definir un comportamiento compartido en una clase padre y ampliarlo en clases hijas especializadas.

En este artículo, exploraremos la herencia en Python, cubriendo conceptos básicos y avanzados como la sobreescritura de métodos y la función super(), que es una función incorporada que devuelve un objeto temporal de la superclase, para que puedas acceder a sus métodos sin nombrar explícitamente la clase padre. No te preocupes si esto aún no tiene sentido, porque lo aprenderemos todo a continuación.

Aprende Python desde cero

Domina Python para la ciencia de datos y adquiere habilidades muy demandadas.
Empieza a aprender gratis

Conceptos básicos de la herencia en Python

La herencia es uno de los pilares fundamentales de la programación orientada a objetos (POO ) que permite que una clase (llamada clase hija) derive atributos y métodos de otra clase (llamada clase padre). Esta característica es fundamental para lareutilización del código y simplifica el mantenimiento, facilitando la construcción de programas escalables y eficientes.

Definir clases padre e hijo

Antes de seguir adelante, exploremos la relación entre las clases padre e hijo.

Clase padre

Empecemos por la clase padre. Una clase padre es la clase base de la que derivan las clases hijas. Encapsula atributos y métodos compartidos.

Utilizando Python, así es como definimos una clase padre:

class ParentClass:
    def __init__(self, attributes):
        # Initialize attributes
        pass

    def method(self):
        # Define behavior
        pass

Clase infantil 

Una clase hija hereda atributos y métodos de la clase padre. Esto le permite utilizar la funcionalidad definida en la clase padre. El código siguiente muestra cómo una clase hija hereda atributos y métodos de una clase padre:

class ChildClass(ParentClass):
    def additional_method(self):
        # Define new behavior
        pass

Esta sencilla sintaxis permite a la clase hija utilizar y ampliar la funcionalidad definida en la clase padre.

Crear una clase padre y una clase hija

Vamos a crear un ejemplo práctico con una clase Person como padre y una clase Student como hijo.

Crear la clase padre

La clase Person contiene atributos compartidos y un método para mostrar información:    

# Defining the Parent Class
class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id
    def display_info(self):
        return f"Name: {self.name}, ID: {self.id}"

Crear la clase hija

La clase Student hereda de Person y añade un nuevo método study.

# Defining the Child Class
class Student(Person):
    def study(self):
        return f"{self.name} is studying."

Vamos a probar las clases padre e hijo.

# Creating and Testing Instances
student = Student("Samuel", 102)
print(student.display_info())  
print(student.study())      
Name: Samuel, ID: 102
Samuel is studying.

Esto es lo que ocurre:

  1. La clase Student utiliza el método __init__ de Person para inicializar name y id.

  2. El método study es exclusivo de la clase Student y amplía su funcionalidad.

  3. El método display_info se hereda directamente de Person.

Tipos de herencia en Python

La herencia en Python permite a las clases heredar atributos y comportamientos de otras clases, lo que favorece la reutilización del código y un diseño limpio, como hemos dicho antes. En esta sección, podemos hablar de los distintos tipos de herencia de Python, que incluye la herencia simple, múltiple, jerárquica e híbrida como categorías separadas.

Herencia única

La herencia única se produce cuando una clase hija hereda de una única clase padre, lo que le permite ampliar la funcionalidad de la padre. Esto es útil cuando un tipo de objeto comparte propiedades comunes con una categoría más amplia, pero también requiere atributos o comportamientos adicionales.

El ejemplo que he empezado a trabajar antes era de herencia simple, pero veámoslo ahora con un poco más de detenimiento: En un sistema de gestión escolar, todos los individuos, incluidos alumnos, profesores y personal, comparten algunos detalles comunes como name y ID. Sin embargo, los alumnos también tienen expedientes académicos, como notas y cursos matriculados. Utilizando la herencia única, podemos crear una clase Person para los atributos compartidos y ampliarla con una claseStudent para los detalles académicos.

herencia única

Herencia única. Imagen del autor

He aquí un buen ejemplo de la situación anterior:

class Person:
    """Represents a general person with basic details."""
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    """Represents a student, extending the Person class to include academic details."""
    def __init__(self, name, id, grade, courses):
        super().__init__(name, id)
        self.grade = grade
        self.courses = courses

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Courses: {', '.join(self.courses)}"

# Example usage in a school system
student = Student("Samuel", 5678, "B+", ["Math", "Physics", "Computer Science"])
print(student.get_details())
Name: Samuel, ID: 5678, Grade: B+, Courses: Math, Physics, Computer Science

La clase Student hereda el método get_details() de Person pero lo amplía para incluir grade y courses. Este es un buen ejemplo de cómo la herencia única promueve lo que se conoce como código modular.

Herencia múltiple

La herencia múltiple, en cierto modo como un árbol genealógico, permite que una clase hija herede de más de una clase padre, combinando atributos y comportamientos de cada una. Esto puede dar lugar a posibles conflictos, que Python resuelve mediante la orden de resolución de métodos (ORM).

herencia múltiple

Herencia múltiple. Imagen del autor

Echa un vistazo:

class Person:
    def get_details(self):
        return "Details of a person."

class Athlete:
    def get_skill(self):
        return "Athletic skills."

class Student(Person, Athlete):
    pass

# Example usage
student = Student()
print(student.get_details())
print(student.get_skill())
Details of a person.
Athletic skills.

Vemos que la clase Student hereda atributos y métodos tanto de Person como de Athlete. Sin ningún esfuerzo adicional, la clase Student tiene acceso al método get_details() de la clase padre Person y al método get_skill() de la clase padre Athlete. Estamos combinando eficazmente la funcionalidad de múltiples fuentes.

Sin embargo, heredar de varias clases puede provocar conflictos. ¿Qué ocurre si ambas clases padre definen un método o atributo con el mismo nombre? Antes he mencionado algo sobre el orden de resolución de los métodos, pero permíteme que te diga algo más al respecto. Orden de resolución de métodos determina el orden de búsqueda de métodos y atributos en las clases. El MRO sigue un enfoque de profundidad, de izquierda a derecha.

Puedes ver el MRO de una clase utilizando el atributo __mro__ o el método mro():

print(Student.__mro__)
(<class '__main__.Student'>, <class '__main__.Person'>, <class '__main__.Athlete'>, <class 'object'>)

Herencia multinivel, jerárquica e híbrida

Python también admite estructuras de herencia más complejas. Mostraré estas ideas más complejas utilizando el mismo ejemplo.

Herencia multinivel

La herencia multinivel se produce cuando una clase hija hereda de otra clase hija, y esa clase hija hereda de una clase padre. Esto crea una cadena de herencia.

herencia multinivel

Herencia multinivel. Imagen del autor

He aquí un buen ejemplo:

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    def __init__(self, name, id, grade):
        super().__init__(name, id)
        self.grade = grade

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

class GraduateStudent(Student):
    def __init__(self, name, id, grade, thesis_title):
        super().__init__(name, id, grade)
        self.thesis_title = thesis_title

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Thesis: {self.thesis_title}"

# Example usage
grad_student = GraduateStudent("Charlie", 91011, "A", "AI in Healthcare")
print(grad_student.get_details())
Name: Charlie, ID: 91011, Grade: A, Thesis: AI in Healthcare

Aquí, cada clase de la cadena añade algo nuevo: Person gestiona nombres e identificaciones, Student incluye calificaciones y GraduateStudent introduce una tesis. Gracias a super().__init__(), reutilizamos la lógica de inicialización sin duplicar código. Es eficaz, ordenado y garantiza que todos los niveles de la "escalera de la herencia", tal como yo la concibo, funcionen.

Herencia jerárquica

En la herencia jerárquica, varias clases hijas heredan de una única clase padre, lo que permite un comportamiento compartido entre subclases con atributos únicos.

herencia jerárquica

Herencia jerárquica. Imagen del autor

Veamos juntos un buen ejemplo:

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    def __init__(self, name, id, grade):
        super().__init__(name, id)
        self.grade = grade

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

class Teacher(Person):
    def __init__(self, name, id, subject):
        super().__init__(name, id)
        self.subject = subject

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Subject: {self.subject}"

# Example usage
student = Student("Samuel", 5678, "B+")
teacher = Teacher("Dr. Smith", 1234, "Math")
print(student.get_details())  
print(teacher.get_details())
Name: Samuel, ID: 5678, Grade: B+
Dr. Smith, ID: 1234, Subject: Math

Aquí, la clase Person sirve de base, ofreciendo atributos y métodos comunes (name, id, y get_details). A continuación, las clases Student y Teacher amplían esta funcionalidad añadiendo sus propiedades exclusivas (grade y subject) y personalizando el método get_details para reflejar sus contextos específicos.

Con este enfoque, la funcionalidad compartida permanece en un solo lugar (la clase Person ), mientras que el comportamiento especializado se encapsula limpiamente en las subclases.

Herencia híbrida

La herencia híbrida combina varios tipos de herencia, como la herencia multinivel o múltiple, para modelar relaciones más complejas.

herencia híbrida

Herencia híbrida. Imagen del autor

Veamos un ejemplo que muestra la complejidad de la herencia híbrida.

# Base class
class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

# Intermediate class inheriting from the base class
class Employee(Person):
    def __init__(self, name, id, position):
        super().__init__(name, id)
        self.position = position

    def get_position(self):
        return f"Position: {self.position}"

# Another independent base class
class Athlete:
    def __init__(self, sport):
        self.sport = sport

    def get_sport(self):
        return f"Sport: {self.sport}"

# Derived class combining Employee and Athlete
class Student(Employee, Athlete):
    def __init__(self, name, id, position, grade, sport):
        Employee.__init__(self, name, id, position)
        Athlete.__init__(self, sport)
        self.grade = grade

    def get_grade(self):
        return f"Grade: {self.grade}"

# Example usage
student = Student("Samuel", 1234, "Intern", "A", "Soccer")
print(student.get_details())  # From Person
print(student.get_position())  # From Employee
print(student.get_grade())  # From Student
print(student.get_sport())  # From Athlete
Name: Samuel, ID: 1234
Position: Intern
Grade: A
Sport: Soccer

En este ejemplo, la clase Student demuestra la herencia híbrida heredando atributos y métodos tanto de Employee (que a su vez hereda de Person) como de Athlete. Esto combinala herencia jerárquica de (donde Employee hereda de Person) y la herencia múltiple (donde Student hereda tanto de Employee como de Athlete ).

Ventajas de la herencia en Python

Ahora, es el momento de ver los puntos fuertes y débiles: 

Beneficios de la herencia

  1. Reutilización: Con la herencia puedes escribir código una vez en la clase padre y reutilizarlo en las clases hijas. Utilizando el ejemplo, tanto FullTimeEmployee como Contractor pueden heredar un método get_details() de la clase padre Employee.

  2. Simplicidad: La herencia modela las relaciones con claridad. Un buen ejemplo es la clase FullTimeEmployee que "es-un" tipo de la clase padre Employee.

  3. Escalabilidad: También añade nuevas funciones o clases hijo sin afectar al código existente. Por ejemplo, podemos añadir fácilmente una nueva clase Intern como clase hija.

Limitaciones potenciales de la herencia

  1. Complejidad: Esto no te sorprenderá, pero demasiados niveles de herencia pueden hacer que el código sea difícil de seguir. Por ejemplo, si un Employee tiene demasiadas clases hijas como Manager, Engineer, Intern, etc., puede resultar confuso.

  2. Dependencia: Los cambios en una clase padre pueden afectar involuntariamente a todas las subclases. Si modificas Employee, por ejemplo, puede que se rompa FullTimeEmployee o Contractor.

  3. Uso indebido: Utilizar la herencia cuando no es lo más adecuado puede complicar los diseños. No querrás crear una solución en la que Car herede de Boat sólo para reutilizar move(). La relación no tiene sentido.

Técnicas avanzadas de herencia en Python

Ahora que hemos explorado los fundamentos de la herencia, veamos algunas técnicas avanzadas. Estas técnicas, como la sustitución de métodos, super(), las clases base abstractas y el polimorfismo, aumentan la flexibilidad del código y permiten patrones de diseño más sofisticados. 

Sobrescribir métodos en python

La sobreescritura de métodos permite a una clase hija proporcionar una implementación específica para un método ya definido en su clase padre. Esto es útil cuando el comportamiento heredado no cumple totalmente los requisitos de la clase hija.

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id
    
    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    def __init__(self, name, id, grade):
        super().__init__(name, id)
        self.grade = grade
    
    # Overriding the get_details method
    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

# Example usage
student = Student("Samuel", 1234, "A")
print(student.get_details())
Name: Samuel, ID: 1234, Grade: A

Aquí, la clase Student anula el método get_details() de la clase Person para dar sus propias implementaciones específicas. Esto permite que la clase hija tenga su propio comportamiento sin dejar de seguir el mismo nombre de método.

Entonces, ¿por qué anulamos? Anulamos porque queremos personalizar el comportamiento heredado y también porque queremos adaptar la funcionalidad de un método padre a los requisitos exclusivos de una clase hija.

Utilizar super() para la inicialización de los padres

La función super() se utiliza para llamar a métodos de la clase padre desde la clase hija. Esto es especialmente útil cuando quieres ampliar o modificar la funcionalidad de un método de una clase padre, como el método constructor __Init__().

Entonces, ¿por qué utilizamos la función super()? Utilizamos la función super porque queremos llamar e inicializar el constructor de la clase padrey también porque queremos evitar nombrar explícitamente la clase padre. Esto es útil, sobre todo en casos de herencia múltiple.

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

class Student(Person):
    def __init__(self, name, id, grade):
        # Using super() to initialize the parent class
        super().__init__(name, id)
        self.grade = grade

# Example usage
student = Student("Samuel", 5678, "B+")
print(student.name)
print(student.id)
print(student.grade)
Samuel
5678
B+

Aquí, la clase Student utiliza super().__init__(name, id) para llamar al método __init__ de la clase padre Person, por lo que no necesita repetir código para inicializar los atributos name y id. A continuación, la clase hija introduce un atributo grade, que es específico de la clase Student.

Clases base abstractas (ABC)

Una clase base abstracta (ABC) es una clase que no puede utilizarse directamente para crear objetos. Su finalidad es definir un conjunto común de métodos que otras clases deben aplicar. Por tanto, los ABC son útiles cuando quieres asegurarte de que determinados métodos estén siempre presentes en las clases hijas.

from abc import ABC, abstractmethod

class Person(ABC):
    @abstractmethod
    def get_details(self):
        pass

class Student(Person):
    def __init__(self, name, id, grade):
        self.name = name
        self.id = id
        self.grade = grade
    
    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

# Example usage
student = Student("Hamilton", 7890, "A-")
print(student.get_details())
Name: Hamilton, ID: 7890, Grade: A-

La clase Person es una clase abstracta que requiere que cualquier clase hija implemente el método get_details(). Este método será implementado posteriormente por la clase hija, Student.

Polimorfismo

Polimorfismo significa muchas formas. En Python, permite que distintas clases utilicen el mismo nombre de método, pero cada una puede implementar ese método de forma distinta.

El polimorfismo nos ayuda a escribir código que puede funcionar con objetos de clases diferentes, aunque esas clases tengan comportamientos distintos:

class Person:
    def get_details(self):
        return "Details of a person."

class Student(Person):
    def get_details(self):
        return "Details of a student."

class Teacher(Person):
    def get_details(self):
        return "Details of a teacher."

# Example usage
def print_details(person):
    print(person.get_details())

student = Student()
teacher = Teacher()

print_details(student) 
print_details(teacher) 
Details of a student.
Details of a teacher.

En este ejemplo, la función print_details() puede aceptar cualquier objeto de tipo Person, pero llamará al método get_details() apropiado en función de si el objeto es un Student o un Teacher.

Errores comunes y buenas prácticas

Aunque la herencia es poderosa, es fácil abusar de ella. Compartiré algunas ideas para ayudarte a aprovecharlas al máximo.

Evitar sorpresas con métodos anulados

Cuando una clase hija anula un método de su padre, el comportamiento puede cambiar.

Por ejemplo, si la clase padre Employee tiene un método calculate_pay(), y la clase hija Manager lo anula sin tener en cuenta todos los escenarios, podría producir cálculos de pago incorrectos.

La mejor práctica, en este caso, es probar siempre a fondo los métodos anulados y documentar su comportamiento.

Elegir entre herencia y composición

Sé que este artículo trata sobre la herencia, pero no siempre es el enfoque correcto. A veces, la composición, en la que construyes clases combinando objetos en lugar de ampliarlos, puede encajar mejor con lo que estés haciendo.

Para resumir las diferencias de la forma más básica, piensa que:

  • La herencia se refiere a las relaciones "Is-a" de . Por ejemplo, un Manager es un Employee.

  • La composición se refiere a las relaciones "Tiene-a". Por ejemplo, un Car tiene un Engine.

Entonces, ¿cómo saber cuándo es mejor utilizar la composición? Utiliza la composición en cuandola relación no sea estrictamente jerárquica y/o cuando quierasreducir el acoplamiento estrecho entre clases.

O también podríamos decir que, mientras que la herencia modela las relaciones, la composición se centra en la funcionalidad. Para ayudarte, considera lo siguiente:

  • Utiliza la herencia cuando los objetos sean jerárquicos por naturaleza. Por ejemplo, Animal > Bird > Parrot.

  • Utiliza la composición cuando los objetos compartan funcionalidad pero no estén relacionados. Por ejemplo, un Printer y un Scanner utilizan ambos un DeviceManager.

Evita las cadenas de herencia profundas

Las cadenas de herencia profundas (muchos niveles de relaciones padre-hijo) pueden hacer que tu código sea difícil de leer y mantener. Esto es un problema porque los cambios en una clase padre pueden afectar involuntariamente a muchas clases hijo. Además, d ebuggingse vuelve complejo a medida que el comportamiento se extiende a través de múltiples niveles.

La mejor práctica en este caso es ketener jerarquías poco profundas. Además, considera utilizar la composición (como he mencionado antes) o dividir una cadena en jerarquías separadas si ves que se está volviendo demasiado profunda.

Conclusión

La herencia es un pilar fundamental de la programación orientada a objetos que permite a los desarrolladores como tú crear código reutilizable, modular y escalable. Si dominas la herencia, te resultará fácil simplificar sistemas complejos.

Una buena forma de profundizar en tus conocimientos es intentar construir estructuras de herencia en tus proyectos. Empieza por lo sencillo y luego experimenta con jerarquías más complejas para ver cómo funcionan en la práctica.

Si tienes ganas de profundizar aún más, puedes consultar nuestro curso Conceptos del paradigma de programación para conocer mejor la herencia y otras ideas. Nuestro itinerario profesional de Desarrollador Python también es un buen recurso que ofrece un camino completo para desarrollar habilidades de programación avanzadas que te equiparán para el desarrollo de software.


Samuel Shaibu's photo
Author
Samuel Shaibu
LinkedIn

Escritora y profesional de los datos con experiencia a la que le apasiona capacitar a los aspirantes a expertos en el espacio de los datos.

Preguntas frecuentes sobre la herencia en Python

¿Qué es la herencia en Python?

La herencia en Python es un mecanismo que permite a una clase heredar atributos y métodos de otra clase, fomentando la reutilización del código y las estructuras jerárquicas de clases.

¿Cómo funciona la herencia en Python?

La herencia en Python funciona definiendo una nueva clase que hereda atributos y métodos de una clase existente, lo que permite estructuras de clases jerárquicas.

¿Cuáles son los tipos de herencia en Python?

Python admite varios tipos de herencia, como la herencia simple, múltiple, multinivel y jerárquica.

¿Cuál es la diferencia entre herencia simple y múltiple en Python?

La herencia simple implica una clase padre, mientras que la herencia múltiple permite que una clase herede de varias clases padre.

¿Cómo se implementa la herencia en Python?

Implementa la herencia definiendo una nueva clase que especifique una clase padre en su definición, utilizando la sintaxis clase ChildClass(ParentClass):.

¿Cuáles son las ventajas de utilizar la herencia en Python?

La herencia fomenta la reutilización del código, permite crear estructuras jerárquicas de clases y admite el polimorfismo, lo que hace que el código sea más flexible y fácil de mantener.

¿Cómo se implementa la herencia múltiple en Python?

La herencia múltiple se implementa definiendo una clase que hereda de más de una clase base, y Python maneja la resolución de métodos utilizando el orden de resolución de métodos (ORM).

Temas
Relacionado

tutorial

Programación orientada a objetos (POO) en Python: Tutorial

Aborda los fundamentos de la Programación Orientada a Objetos (POO) en Python: explora las clases, los objetos, los métodos de instancia, los atributos y ¡mucho más!
Théo Vanderheyden's photo

Théo Vanderheyden

12 min

tutorial

Tutorial de Python sobre conjuntos y teoría de conjuntos

Aprende sobre los conjuntos en Python: qué son, cómo crearlos, cuándo usarlos, funciones incorporadas y su relación con las operaciones de la teoría de conjuntos.
DataCamp Team's photo

DataCamp Team

13 min

tutorial

Tutorial sobre cómo trabajar con módulos en Python

Los módulos te permiten dividir partes de tu programa en archivos diferentes para facilitar el mantenimiento y mejorar el rendimiento.

Nishant Kumar

8 min

tutorial

21 herramientas esenciales de Python

Conozca las herramientas esenciales de Python para el desarrollo de software, raspado y desarrollo web, análisis y visualización de datos y aprendizaje automático.
Abid Ali Awan's photo

Abid Ali Awan

6 min

tutorial

Aprendizaje automático de datos categóricos con el tutorial de Python

Aprenda los trucos más comunes para manejar datos categóricos y preprocesarlos para construir modelos de aprendizaje automático.
Moez Ali's photo

Moez Ali

28 min

tutorial

Manejo de Excepciones y Errores en Python

Los errores y las excepciones pueden provocar fallos en el programa o un comportamiento inesperado, y Python viene con un sólido conjunto de herramientas para mejorar la estabilidad del código.
Abid Ali Awan's photo

Abid Ali Awan

21 min

Ver másVer más