Resumen
- Aprende sobre la Herencia en la Programación Orientada a Objetos y las distintas formas de Herencia
- Entérate sobre el Método Overriding y la función super() en el mundo de la Programación Orientada a Objetos
- Conceptos básicos de la programación orientada a objetos.
Introducción
La Herencia es uno de los aspectos más importantes de la Programación Orientada a Objetos (POO). La clave para entender la Herencia es que proporciona reutilización de código. En lugar de escribir el mismo código, una y otra vez, podemos simplemente heredar las propiedades de una clase en la otra.
Esto, como puedes imaginar, ahorra una tonelada de tiempo. Y el tiempo es dinero en la ciencia de los datos!
La programación orientada a objetos trata de objetos del mundo real y la herencia es una forma de representar las relaciones del mundo real. Aquí hay un ejemplo – coche, autobús, bicicleta – todos ellos entran en una categoría más amplia llamada Vehículo. Eso significa que han heredado las propiedades de la clase vehículos, es decir, todos se utilizan para el transporte.
Podemos representar esta relación en el código con la ayuda de la herencia.
Otra cosa intrigante acerca de la herencia es que es transitiva en la naturaleza. Pero, ¿qué significa esto? Lo veremos en detalle más adelante en este artículo. Python también soporta varios tipos de herencia que cubriré en detalle en este artículo.
Este es el segundo artículo de la serie de artículos relacionados con la Programación Orientada a Objetos. Por favor, revise el primer artículo también:
Tabla de contenidos
- ¿Qué es la herencia en la programación orientada a objetos?
- Diferentes formas de herencia en la programación orientada a objetos
- Herencia simple
- Herencia múltiple
- Multi-nivel de herencia
- Herencia jerárquica
- Herencia híbrida
- Sobreescritura de métodos
- La función super()
¿Qué es la herencia en la programación orientada a objetos?
La herencia es el procedimiento por el cual una clase hereda los atributos y métodos de otra clase. La clase cuyas propiedades y métodos se heredan se conoce como clase Padre. Y la clase que hereda las propiedades de la clase padre es la clase hija.
Lo interesante es que, junto con las propiedades y métodos heredados, una clase hija puede tener sus propias propiedades y métodos.
Puedes utilizar la siguiente sintaxis:
class parent_class:body of parent classclass child_class( parent_class):body of child class
Veamos la implementación:
class Car: #parent class def __init__(self, name, mileage): self.name = name self.mileage = mileage def description(self): return f"The {self.name} car gives the mileage of {self.mileage}km/l"class BMW(Car): #child class passclass Audi(Car): #child class def audi_desc(self): return "This is the description method of class Audi."
obj1 = BMW("BMW 7-series",39.53)print(obj1.description())obj2 = Audi("Audi A8 L",14)print(obj2.description())print(obj2.audi_desc())
Salida:
Hemos creado dos clases hijas llamadas «BMW» y «Audi» que han heredado los métodos y propiedades de la clase padre «Coche». No hemos proporcionado funciones y métodos adicionales en la clase BMW. Mientras que hay un método adicional dentro de la clase Audi.
Nota cómo el método de instancia description() de la clase padre es accesible por los objetos de las clases hijas con la ayuda de obj1.description() y obj2.description(). Y el método independiente de la clase Audi también es accesible utilizando obj2.audi_desc().
Podemos comprobar la clase base o padre de cualquier clase utilizando un atributo de clase incorporado __bases__
print(BMW.__bases__, Audi.__bases__)
print( Car.__bases__ )
Formas de herencia en la programación orientada a objetos
A grandes rasgos, existen cinco formas de herencia basadas en la participación de las clases padre e hijo.
1. Herencia simple
Es una forma de herencia en la que una clase hereda sólo una clase padre. Esta es la forma simple de herencia y por lo tanto también se conoce como herencia simple.
class Parent: def f1(self): print("Function of parent class.")class Child(Parent): def f2(self): print("Function of child class.")object1 = Child()object1.f1()object1.f2()
2. Herencia múltiple
Una herencia se convierte en herencia múltiple cuando una clase hereda más de una clase padre. La clase hija después de heredar propiedades de varias clases padre tiene acceso a todos sus objetos.
class Parent_1: def f1(self): print("Function of parent_1 class.")class Parent_2: def f2(self): print("Function of parent_2 class.")class Parent_3: def f3(self): print("function of parent_3 class.")class Child(Parent_1, Parent_2, Parent_3): def f4(self): print("Function of child class.")object_1 = Child()object_1.f1()object_1.f2()object_1.f3()object_1.f4()
Aquí tenemos una clase hija que está heredando propiedades de tres clases padre_1, padre_2 y padre_3. Todas las clases tienen diferentes funciones y todas las funciones son llamadas usando el objeto de la clase Child.
class Parent_1: def f1(self): print("Function of parent_1 class.")class Parent_2: def f1(self): print("Function of parent_2 class.")class Child(Parent_1, Parent_2): def f2(self): print("Function of child class.")
Aquí, las clases Parent_1 y Parent_2 tienen la misma función f1(). Ahora, cuando el objeto de la clase Hijo llama a f1(), ya que la clase Hijo está heredando las dos clases padre, ¿qué crees que debería pasar?
obj = Child() obj.f1()
Pero, ¿por qué no se hereda la función f1() de la clase Padre_2?
En la herencia múltiple, la clase hijo busca primero el método en su propia clase. Si no lo encuentra, entonces busca en las clases padre en orden profundidad_primero e izquierda-derecha. Como este era un ejemplo fácil con sólo dos clases padre, podemos ver claramente que la clase Padre_1 fue heredada primero, por lo que la clase hija buscará el método en la clase Padre_1 antes de buscar en la clase Padre_2.
Pero para problemas de herencia complicados, se hace difícil identificar el orden. Así que la forma actual de hacer esto se llama Orden de Resolución de Métodos (MRO) en Python. Podemos encontrar el MRO de cualquier clase usando el atributo __mro__
Child.__mro__
Esto nos dice que la clase Child visitó primero la clase Parent_1 y luego Parent_2, por lo que se llamará al método f1() de Parent_1.
Tomemos un ejemplo un poco complicado en Python:
class Parent_1:passclass Parent_2:passclass Parent_3:passclass Child_1(Parent_1,Parent_2):passclass Child_2(Parent_2,Parent_3):passclass Child_3(Child_1,Child_2,Parent_3):pass
Aquí, la clase Child_1 está heredando dos clases – Parent_1 y Parent_2. La clase Niño_2 también está heredando dos clases – Padre_2 y Padre_3. Otra clase Child_3 está heredando tres clases – Child_1, Child_2 y Parent_3.
Ahora, sólo mirando esta herencia, es bastante difícil determinar el Orden de Resolución de Métodos para la clase Child_3. Así que aquí está el uso real de __mro__-
Child_3.__mro__
Podemos ver que primero, el intérprete busca en Child_3, luego en Child_1 seguido de Parent_1, Child_2, Parent_2 y Parent_3 respectivamente.
3. Herencia multinivel
Por ejemplo, una clase_1 es heredada por una clase_2 y esta clase_2 también es heredada por la clase_3 y este proceso continúa. Esto se conoce como herencia multinivel. Entendamos con un ejemplo:
class Parent: def f1(self): print("Function of parent class.")class Child_1(Parent): def f2(self): print("Function of child_1 class.")class Child_2(Child_1): def f3(self): print("Function of child_2 class.")obj_1 = Child_1()obj_2 = Child_2()obj_1.f1()obj_1.f2()print("\n")obj_2.f1()obj_2.f2()obj_2.f3()
Aquí, la clase Niño_1 está heredando la clase Padre y la clase Niño_2 está heredando la clase Niño_1. En esta Child_1 tiene acceso a las funciones f1() y f2() mientras que Child_2 tiene acceso a las funciones f1(), f2() y f3(). Si intentamos acceder a la función f3() utilizando el objeto de la clase Clase_1 entonces se producirá un error indicando:
El objeto ‘Child_1’ no tiene el atributo ‘f3’
obj_1.f3()
4- Herencia jerárquica
En esto, varias clases Child heredan una única clase Parent. El ejemplo dado en la introducción de la herencia es un ejemplo de herencia jerárquica ya que las clases BMW y Audi heredan de la clase Car.
Para simplificar veamos otro ejemplo:
class Parent:deff1(self):print("Function of parent class.")class Child_1(Parent):deff2(self):print("Function of child_1 class.")class Child_2(Parent):deff3(self):print("Function of child_2 class.")obj_1 = Child_1()obj_2 = Child_2()obj_1.f1()obj_1.f2()print('\n')obj_2.f1()obj_2.f3()
Aquí hay dos clases hijas que heredan de la misma clase Padre. La clase Hijo_1 tiene acceso a las funciones f1() de la clase Padre y a la función f2() de ella misma. Mientras que la clase Hijo_2 tiene acceso a las funciones f1() de la clase Padre y a la función f3() de sí misma.
5- Herencia híbrida
Cuando hay una combinación de más de una forma de herencia, se conoce como herencia híbrida. Quedará más claro después de este ejemplo:
class Parent: def f1(self): print("Function of parent class.")class Child_1(Parent): def f2(self): print("Function of child_1 class.")class Child_2(Parent): def f3(self): print("Function of child_2 class.")class Child_3(Child_1, Child_2): def f4(self): print("Function of child_3 class.")obj = Child_3()obj.f1()obj.f2()obj.f3()obj.f4()
En este ejemplo, dos clases ‘Hijo_1′ e ‘Hijo_2’ se derivan de la clase base ‘Padre’ utilizando la herencia jerárquica. Otra clase ‘Niño_3’ se deriva de las clases ‘Niño_1’ y ‘Niño_2’ utilizando herencia múltiple. La clase ‘Child_3’ se deriva ahora utilizando la herencia híbrida.
Sobreescritura de métodos
El concepto de sobreescritura es muy importante en la herencia. Da la capacidad especial a las clases hijas/subclases de proporcionar una implementación específica a un método que ya está presente en sus clases padre.
class Parent: def f1(self): print("Function of Parent class.")class Child(Parent): def f1(self): print("Function of Child class.")obj = Child()obj.f1()
Aquí la función f1() de la clase Hijo ha sobrescrito la función f1() de la clase Padre. Cada vez que el objeto de la clase Child invoca f1(), la función de la clase Child se ejecuta. Sin embargo, el objeto de la clase Parent puede invocar la función f1() de la clase parent.
obj_2 = Parent()obj_2.f1()
La función super()
La función super() en Python devuelve un objeto proxy que hace referencia a la clase parent usando la palabra clave super. Esta palabra clave super() es básicamente útil para acceder a los métodos anulados de la clase padre.
- En una jerarquía de clases con herencia simple, super ayuda a referirse a las clases padre sin nombrarlas explícitamente, haciendo así el código más mantenible.
Por ejemplo-
class Parent: def f1(self): print("Function of Parent class.")class Child(Parent): def f1(self): super().f1() print("Function of Child class.")obj = Child()obj.f1()
Aquí, con la ayuda de super().f1(), el método f1() de la clase super de la clase Child i.e clase Padre ha sido llamado sin nombrarlo explícitamente.Una cosa a tener en cuenta aquí es que la clase super() puede aceptar dos parámetros- el primero es el nombre de la subclase y el segundo es un objeto que es una instancia de esa subclase. Veamos cómo-class Parent: def f1(self): print("Function of Parent class.")class Child(Parent): def f1(self): super( Child, self ).f1() print("Function of Child class.")obj = Child()obj.f1()
El primer parámetro se refiere a la subclase Child, mientras que el segundo se refiere al objeto de Child que, en este caso, es self. Puedes ver que la salida después de usar super() y super( Child, self) es la misma porque, en Python 3, super( Child, self) es equivalente a self().Ahora veamos un ejemplo más usando la función __init__.class Parent(object): def__init__(self, ParentName): print(ParentName, 'is derived from another class.')class Child(Parent): def__init__(self, ChildName): print(name,'is a sub-class.') super().__init__(ChildName)obj = Child('Child')
Lo que hemos hecho aquí es que hemos llamado a la función __init__ de la clase Parent (dentro de la clase Child) usando super().__init__( ChildName ). Y como el método __init__ de la clase Parent requiere un argumento, éste ha sido pasado como «ChildName». Así que después de crear el objeto de la clase Child, primero se ejecutó la función __init__ de la clase Child, y después la función __init__ de la clase Parent.
- El segundo caso de uso es soportar las herencias múltiples cooperativas en un entorno de ejecución dinámico.
class First(): def __init__(self): print("first") super().__init__()class Second(): def __init__(self): print("second") super().__init__()class Third(Second, First): def __init__(self): print("third") super().__init__()obj = Third()
La llamada a super() encuentra el siguiente método en la ORM a cada paso, por lo que First y Second tienen que tenerlo también, de lo contrario, la ejecución se detiene al final de first().
Nótese que la superclase tanto de First como de Second es Object.
Busquemos también el MRO de Third().
Third.__mro__
El orden es Third > Second > First y el mismo es el orden de nuestra salida.
Notas finales
Para concluir, en este artículo he llevado adelante el concepto de Herencia en la Programación Orientada a Objetos en Python. Cubrí varias formas de Herencia y algunos de los conceptos comunes en la Herencia como el Anulado de Métodos y la función super().
Espero que hayas entendido los conceptos explicados en este artículo. Hazme saber en los comentarios de abajo si tienes alguna duda.
También puedes leer este artículo en nuestra APP móvil
- El segundo caso de uso es soportar las herencias múltiples cooperativas en un entorno de ejecución dinámico.
0 comentarios