Acordeón de los Índices
Herencia de Python
🐍 Herencia en Python: Reutilización y Extensión de Clases
La herencia es un mecanismo poderoso en la programación orientada a objetos que permite crear una nueva clase (clase hija o subclase) basada en una clase existente (clase padre o superclase). La clase hija hereda los atributos y métodos de la clase padre, lo que fomenta la reutilización de código y la creación de jerarquías de clases relacionadas.
Conceptos Clave de la Herencia:
- Clase Padre (Superclase o Clase Base): Es la clase de la que se hereda. Define atributos y métodos comunes que pueden ser compartidos por sus clases hijas.
- Clase Hija (Subclase o Clase Derivada): Es la nueva clase que hereda de la clase padre. Puede añadir nuevos atributos y métodos, o modificar los heredados.
- Reutilización de Código: La herencia permite reutilizar el código de la clase padre en las clases hijas, evitando la duplicación y facilitando el mantenimiento.
- Extensibilidad: Las clases hijas pueden extender la funcionalidad de la clase padre añadiendo nuevos comportamientos o atributos específicos.
- Jerarquía de Clases: La herencia puede crear jerarquías de clases, donde las clases más generales se encuentran en la parte superior y las clases más específicas en la parte inferior.
Beneficios de la Herencia:
- Mayor organización del código: Permite estructurar las clases de forma lógica y jerárquica.
- Reducción de la redundancia: El código común se define una sola vez en la clase padre y se comparte entre las clases hijas.
- Facilita la extensión y modificación: Se pueden añadir nuevas funcionalidades o modificar las existentes en las clases hijas sin alterar la clase padre (en muchos casos).
- Promueve el polimorfismo: La herencia es una base para el polimorfismo, donde objetos de diferentes clases pueden responder al mismo método de manera diferente. (Lo veremos en lecciones futuras).
Sintaxis Básica de la Herencia en Python:
Para indicar que una clase hereda de otra en Python, se especifica la clase padre entre paréntesis después del nombre de la clase hija en la definición de la clase.
class ClasePadre:
# Atributos y métodos de la clase padre
pass
class ClaseHija(ClasePadre):
# Atributos y métodos de la clase hija
# Hereda automáticamente los de ClasePadre
pass
Ejemplo Introductorio: Animal
y Perro
Consideremos un ejemplo sencillo donde tenemos una clase padre Animal
con
una característica común a todos los animales (por ejemplo, hacer un sonido) y una clase
hija Perro
que hereda de Animal
y tiene un sonido específico.
class Animal:
def hacer_sonido(self):
print("El animal hace un sonido genérico.")
class Perro(Animal):
def ladrar(self):
print("¡Guau! ¡Guau!")
mi_animal = Animal()
mi_perro = Perro()
mi_animal.hacer_sonido() # Output: El animal hace un sonido genérico.
mi_perro.hacer_sonido() # Output: El animal hace un sonido genérico. (Heredado de Animal)
mi_perro.ladrar() # Output: ¡Guau! ¡Guau! (Propio de Perro)
En este ejemplo, la clase Perro
hereda el método hacer_sonido()
de la clase Animal
y además define su propio método ladrar()
.
En los siguientes temas, exploraremos cómo crear clases padre e hija con más detalle,
cómo utilizar el método __init__()
en la herencia y la función
super()
, así como cómo añadir propiedades y métodos específicos a las
clases hijas.
Crear una clase de padres
👨👩👧👦 Crear una Clase de Padres (Superclase)
La clase de padres es la base de la herencia. Contiene los atributos y métodos que serán comunes a una o más clases hijas. Al definir una clase de padres, nos enfocamos en las características y comportamientos generales que comparten los objetos de las clases más específicas que heredarán de ella.
Sintaxis para Crear una Clase de Padres:
La sintaxis para crear una clase de padres es la misma que para cualquier otra clase en Python:
class NombreDeLaClasePadre:
def __init__(self, atributo1, atributo2, ...):
# Inicialización de atributos comunes
self.atributo1 = atributo1
self.atributo2 = atributo2
# ...
def metodo_comun1(self):
# Implementación de un método común
pass
def metodo_comun2(self, parametro):
# Implementación de otro método común
pass
- Definimos la clase utilizando la palabra clave
class
seguida del nombre de la clase padre (por convención, con la primera letra en mayúscula). - Podemos incluir un método
__init__()
para inicializar los atributos comunes de los objetos de esta clase y de sus clases hijas. - Podemos definir métodos que representen comportamientos comunes.
Ejemplo: La Clase de Padres Vehiculo
Consideremos un ejemplo donde queremos modelar diferentes tipos de vehículos. Podemos
crear una clase padre llamada Vehiculo
que contenga atributos y métodos
comunes a todos los vehículos, como una marca, un modelo y la capacidad de moverse.
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def moverse(self):
print("El vehículo se está moviendo.")
def obtener_informacion(self):
return f"Marca: {self.marca}, Modelo: {self.modelo}"
# Podemos crear objetos de la clase Vehiculo directamente
mi_vehiculo = Vehiculo("Genérico", "Estándar")
print(mi_vehiculo.obtener_informacion())
mi_vehiculo.moverse()
En este ejemplo, la clase Vehiculo
tiene un constructor que inicializa la
marca y el modelo, un método para simular el movimiento y otro para obtener información
básica del vehículo.
Próximos Pasos: Crear Clases Hijas
Una vez que tenemos definida nuestra clase de padres Vehiculo
, podemos crear
clases hijas más específicas, como Coche
, Moto
o
Bicicleta
, que heredarán las características de Vehiculo
y
podrán añadir sus propias particularidades.
Crear una clase de niño
👶 Crear una Clase Hija (Subclase)
Una clase hija hereda atributos y métodos de su clase padre. Podemos definir nuevas características y comportamientos específicos para la clase hija, o incluso modificar los heredados (lo que se conoce como "sobrescritura").
Sintaxis para Crear una Clase Hija:
Para crear una clase hija, se especifica el nombre de la clase padre entre paréntesis después del nombre de la clase hija en la definición de la clase.
class ClaseHija(ClasePadre):
def __init__(self, atributo_padre1, atributo_padre2, atributo_hijo):
# Inicialización de atributos
super().__init__(atributo_padre1, atributo_padre2)
self.atributo_hijo = atributo_hijo
def metodo_hijo(self):
# Implementación de un método específico de la clase hija
pass
def metodo_padre_modificado(self):
# Sobrescribe un método de la clase padre
pass
- Definimos la clase hija utilizando la palabra clave
class
seguida de su nombre y el nombre de la clase padre entre paréntesis (ej:class Coche(Vehiculo):
). - En el método
__init__()
de la clase hija, es común llamar al método__init__()
de la clase padre utilizando la funciónsuper()
para inicializar los atributos heredados. - Podemos añadir nuevos atributos específicos de la clase hija (ej:
self.atributo_hijo
). - Podemos definir nuevos métodos que son propios de la clase hija (ej:
metodo_hijo()
). - Podemos sobrescribir métodos de la clase padre para proporcionar una implementación
específica para la clase hija (ej:
metodo_padre_modificado()
).
Ejemplo: La Clase Hija Coche
que Hereda de Vehiculo
Vamos a crear una clase hija llamada Coche
que hereda de nuestra clase padre
Vehiculo
. Un coche tendrá atributos adicionales como el número de ruedas y
la capacidad de aparcar.
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def moverse(self):
print("El vehículo se está moviendo.")
def obtener_informacion(self):
return f"Marca: {self.marca}, Modelo: {self.modelo}"
class Coche(Vehiculo):
def __init__(self, marca, modelo, num_ruedas):
super().__init__(marca, modelo) # Llama al __init__ de la clase padre
self.num_ruedas = num_ruedas
def aparcar(self):
print("El coche está aparcando.")
# Sobrescribiendo el método moverse de la clase padre
def moverse(self):
print("El coche está conduciendo.")
def obtener_informacion(self):
info_padre = super().obtener_informacion()
return f"{info_padre}, Número de ruedas: {self.num_ruedas}"
# Crear un objeto de la clase Coche
mi_coche = Coche("Ford", "Fiesta", 4)
print(mi_coche.obtener_informacion())
mi_coche.moverse()
mi_coche.aparcar()
En este ejemplo, la clase Coche
hereda la marca y el modelo de
Vehiculo
, tiene su propio atributo num_ruedas
y su propio
método aparcar()
. Además, sobrescribe el método moverse()
para
proporcionar un comportamiento más específico para un coche y también modifica el método
obtener_informacion()
utilizando super()
para extender la
información del padre.
En el siguiente tema, profundizaremos en la función __init__()
y cómo se
utiliza en la herencia.
Añadir la función "init"()
⚙️ Añadir la Función __init__()
en Clases
Hijas
El método especial __init__()
es el constructor de una clase. Se llama
automáticamente cuando se crea un nuevo objeto de esa clase. En el contexto de la
herencia, la forma en que manejamos el __init__()
en la clase hija es
fundamental para asegurar que tanto los atributos de la clase padre como los de la clase
hija se inicialicen correctamente.
Cuando la Clase Hija Tiene su Propio __init__()
:
Si defines un método __init__()
en la clase hija, éste sobrescribirá
el __init__()
de la clase padre. Esto significa que si no
llamas explícitamente al __init__()
de la clase padre desde el
__init__()
de la clase hija, los atributos definidos en el constructor del
padre no se inicializarán para los objetos de la clase hija.
La Importancia de Llamar a __init__()
del Padre:
Para evitar la pérdida de la inicialización de los atributos del padre, es crucial llamar
al método __init__()
de la clase padre dentro del __init__()
de la clase hija. Esto asegura que los atributos heredados se configuren correctamente
antes de que se inicialicen los atributos específicos de la clase hija.
Cómo Llamar a __init__()
del Padre:
Hay dos formas principales de llamar al método __init__()
de la clase padre
desde la clase hija:
- Usando el nombre de la clase padre directamente:
En este caso, debes pasar explícitamenteclass ClaseHija(ClasePadre): def __init__(self, atributo_padre, atributo_hijo): ClasePadre.__init__(self, atributo_padre) self.atributo_hijo = atributo_hijo
self
como el primer argumento al método__init__()
de la clase padre. - Usando la función
super()
:
La funciónclass ClaseHija(ClasePadre): def __init__(self, atributo_padre, atributo_hijo): super().__init__(atributo_padre) self.atributo_hijo = atributo_hijo
super()
proporciona una forma más elegante y flexible de acceder a los métodos de la clase padre, especialmente en situaciones de herencia múltiple (que veremos más adelante). No necesitas pasarself
explícitamente cuando usassuper()
.
Ejemplo: __init__()
en las Clases Vehiculo
y
Coche
Volvamos a nuestro ejemplo de Vehiculo
y Coche
para ilustrar el
uso de __init__()
.
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def obtener_informacion(self):
return f"Marca: {self.marca}, Modelo: {self.modelo}"
class Coche(Vehiculo):
def __init__(self, marca, modelo, num_ruedas):
super().__init__(marca, modelo) # Llama al __init__ de Vehiculo
self.num_ruedas = num_ruedas
def obtener_informacion(self):
info_padre = super().obtener_informacion()
return f"{info_padre}, Número de ruedas: {self.num_ruedas}"
mi_coche = Coche("Seat", "León", 4)
print(mi_coche.obtener_informacion())
En la clase Coche
, el método __init__()
primero llama a
super().__init__(marca, modelo)
para inicializar los atributos
marca
y modelo
heredados de Vehiculo
, y luego
inicializa el atributo específico de Coche
, num_ruedas
.
Cuándo No Definir __init__()
en la Clase Hija:
Si la clase hija no necesita inicializar ningún atributo adicional más allá de los que
hereda de la clase padre, puedes omitir la definición de un método
__init__()
en la clase hija. En este caso, se utilizará automáticamente el
__init__()
de la clase padre cuando se cree un objeto de la clase hija.
En el siguiente tema, exploraremos con más detalle la función super()
y sus
ventajas en la herencia.
Utilice la función súper()
🦸♂️ Utilice la Función super()
:
Accediendo a la Clase Padre
La función super()
en Python se utiliza para llamar a métodos de la clase
padre (o superclase) desde la clase hija (o subclase). Proporciona una forma limpia y
flexible de interactuar con la jerarquía de herencia, especialmente en casos de herencia
múltiple.
¿Qué hace super()
?
Cuando se llama dentro de un método de una clase hija, super()
devuelve un
objeto proxy que delega las llamadas de métodos a una clase padre. La principal ventaja
de usar super()
sobre la llamada directa al nombre de la clase padre (ej:
ClasePadre.metodo(self, ...)
) es su flexibilidad y su capacidad para
manejar correctamente la herencia múltiple y el orden de resolución de métodos (MRO -
Method Resolution Order).
Sintaxis de super()
:
La forma más común de usar super()
es sin argumentos:
super().metodo_de_la_padre(...)
También se puede usar con la clase hija y la instancia como argumentos (ej:
super(ClaseHija, self)
), pero la forma sin argumentos es generalmente
preferida en el código moderno de Python 3.
Ventajas de Usar super()
:
- Robustez en herencia múltiple:
super()
sigue el MRO para determinar qué clase padre se llama, lo que es crucial en situaciones de herencia múltiple complejas. - Código más limpio y mantenible: No necesitas referenciar explícitamente el nombre de la clase padre, lo que hace que el código sea más fácil de refactorizar si la jerarquía de clases cambia.
- Colaboración entre clases en la jerarquía: Permite que los métodos en diferentes niveles de la jerarquía de herencia cooperen y extiendan la funcionalidad sin sobrescribirse completamente.
Ejemplo: Usando super()
en __init__()
y Otros Métodos
Volvamos a nuestro ejemplo de Vehiculo
y Coche
para ver cómo
super()
se utiliza tanto en el constructor como en otros métodos.
class Vehiculo:
def __init__(self, marca, modelo):
print("Inicializando Vehiculo")
self.marca = marca
self.modelo = modelo
def obtener_informacion(self):
return f"Vehículo - Marca: {self.marca}, Modelo: {self.modelo}"
class Coche(Vehiculo):
def __init__(self, marca, modelo, num_ruedas):
print("Inicializando Coche")
super().__init__(marca, modelo) # Llama al __init__ de Vehiculo
self.num_ruedas = num_ruedas
def obtener_informacion(self):
info_padre = super().obtener_informacion()
return f"{info_padre}, Número de ruedas: {self.num_ruedas}"
def moverse(self):
print("El coche está conduciendo.")
mi_coche = Coche("BMW", "Serie 3", 4)
print(mi_coche.obtener_informacion())
mi_coche.moverse()
En este ejemplo:
- En el
__init__()
deCoche
,super().__init__(marca, modelo)
llama al constructor de la claseVehiculo
para inicializar lamarca
y elmodelo
. - En el método
obtener_informacion()
deCoche
,super().obtener_informacion()
llama al métodoobtener_informacion()
de la claseVehiculo
, cuyo resultado se utiliza para construir una cadena de información más completa.
Consideraciones al Usar super()
:
super()
depende de la correcta definición del MRO de la clase.- En la mayoría de los casos de herencia simple, su uso es directo y recomendado.
- En herencia múltiple, comprender el MRO es crucial para predecir el comportamiento
de
super()
.
En resumen, super()
es una herramienta poderosa para trabajar con la
herencia en Python, permitiendo una colaboración fluida y flexible entre las clases en
la jerarquía.
En los siguientes temas, veremos cómo añadir propiedades y métodos específicos a las clases hijas.
Añadir Propiedades
➕ Añadir Propiedades (Atributos) Específicos a las Clases Hijas
Las clases hijas no solo heredan los atributos de sus clases padres, sino que también pueden definir sus propias propiedades únicas. Estas propiedades representan las características específicas de los objetos de la clase hija.
Dónde Definir las Propiedades de la Clase Hija:
Las propiedades específicas de una clase hija generalmente se definen e inicializan
dentro de su método __init__()
. Después de llamar al
__init__()
de la clase padre (usando super().__init__(...)
),
puedes añadir las inicializaciones para los atributos propios de la clase hija.
Ejemplo: Añadiendo Propiedades a la Clase Coche
En nuestro ejemplo de Vehiculo
y Coche
, la clase
Coche
tiene una propiedad específica: el número de ruedas (`num_ruedas`).
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def obtener_informacion(self):
return f"Vehículo - Marca: {self.marca}, Modelo: {self.modelo}"
class Coche(Vehiculo):
def __init__(self, marca, modelo, num_ruedas, tipo_motor):
super().__init__(marca, modelo) # Inicializa marca y modelo del padre
self.num_ruedas = num_ruedas # Propiedad específica de Coche
self.tipo_motor = tipo_motor # Otra propiedad específica de Coche
def obtener_informacion_completa(self):
info_padre = super().obtener_informacion()
return f"{info_padre}, Número de ruedas: {self.num_ruedas}, Tipo de motor: {self.tipo_motor}"
mi_coche = Coche("Renault", "Clio", 4, "Gasolina")
print(mi_coche.obtener_informacion_completa())
En la clase Coche
, dentro del método __init__()
:
- Primero, llamamos a
super().__init__(marca, modelo)
para inicializar los atributos heredados deVehiculo
(`marca` y `modelo`). - Luego, inicializamos las propiedades específicas de
Coche
:self.num_ruedas = num_ruedas
yself.tipo_motor = tipo_motor
.
Además, hemos añadido un método obtener_informacion_completa()
que utiliza
la información del padre y añade las propiedades específicas del coche.
Otros Métodos para Acceder y Modificar Propiedades:
Al igual que en las clases base, en las clases hijas también podemos definir otros
métodos para acceder (`getters`) y modificar (`setters`) las propiedades, si queremos
controlar cómo se leen o escriben estos atributos. Sin embargo, para propiedades
simples, el acceso directo (ej: mi_coche.num_ruedas
) suele ser suficiente.
La Flexibilidad de la Herencia:
La capacidad de añadir propiedades específicas en las clases hijas es una de las ventajas clave de la herencia. Permite crear clases más especializadas que mantienen las características generales de su clase padre pero también incorporan detalles que las hacen únicas.
En el siguiente tema, exploraremos cómo añadir métodos específicos a las clases hijas.
Añadir métodos
✍️ Añadir Métodos Específicos a las Clases Hijas
Además de heredar los métodos de su clase padre, una clase hija puede definir sus propios métodos que son exclusivos de su tipo. Estos métodos implementan comportamientos o funcionalidades específicas de los objetos de la clase hija.
Dónde Definir los Métodos de la Clase Hija:
Los métodos específicos de una clase hija se definen dentro del cuerpo de la definición de la clase, al igual que cualquier otro método. Estos métodos pueden utilizar los atributos heredados de la clase padre y los atributos propios de la clase hija para realizar sus acciones.
Ejemplo: Añadiendo Métodos a la Clase Coche
En nuestro ejemplo de Vehiculo
y Coche
, podemos añadir métodos
específicos para la clase Coche
, como abrir_puerta()
o
encender_motor()
.
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def obtener_informacion(self):
return f"Vehículo - Marca: {self.marca}, Modelo: {self.modelo}"
class Coche(Vehiculo):
def __init__(self, marca, modelo, num_ruedas):
super().__init__(marca, modelo)
self.num_ruedas = num_ruedas
def obtener_informacion_completa(self):
info_padre = super().obtener_informacion()
return f"{info_padre}, Número de ruedas: {self.num_ruedas}"
def moverse(self):
print("El coche está conduciendo.")
def abrir_puerta(self, numero_puerta):
print(f"Abriendo la puerta número {numero_puerta} del coche.")
def encender_motor(self):
print("El motor del coche se ha encendido.")
mi_coche = Coche("Peugeot", "208", 4)
print(mi_coche.obtener_informacion_completa())
mi_coche.moverse()
mi_coche.abrir_puerta(2)
mi_coche.encender_motor()
En la clase Coche
, hemos añadido dos métodos específicos:
abrir_puerta(self, numero_puerta)
: Permite simular la apertura de una puerta específica del coche.encender_motor(self)
: Simula el encendido del motor del coche.
Estos métodos son exclusivos de la clase Coche
y no están presentes en la
clase padre Vehiculo
.
Combinación de Métodos Heredados y Propios:
Una de las ventajas de la herencia es que los objetos de la clase hija pueden utilizar tanto los métodos que heredan de la clase padre como los métodos que se definen específicamente en la clase hija, lo que permite crear objetos con un conjunto de funcionalidades ricas y especializadas.
Sobrescritura de Métodos:
Recuerda que una clase hija también puede sobrescribir (redefinir) un método heredado de
la clase padre para proporcionar una implementación específica para la clase hija. Vimos
un ejemplo de esto con el método moverse()
en la clase Coche
.
Con esto, hemos cubierto los temas principales de la herencia en Python. ¡Espero que esta lección haya sido clara y útil!
0 Comentarios
Si desea contactar comigo, lo puede hacer atravez deste formulario gracias