Herencia en la programación orientada a objetos

Contenidos

Visión general

  • Aprenda sobre la herencia en la programación orientada a objetos y varias formas de herencia
  • Comprender la anulación de métodos y la función super () en el mundo 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 (OOP). La clave para comprender la herencia es que proporciona reutilización del 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 puede imaginar, ahorra mucho tiempo. ¡Y el tiempo es dinero en ciencia de datos!

inheritance-in-oop-9792497

La programación orientada a objetos se trata de objetos del mundo real y la herencia es una forma de representar las relaciones del mundo real. He aquí un ejemplo: coche, autobús, bicicleta – todos ellos pertenecen a una categoría más amplia denominada Vehículo. Eso significa que han heredado las propiedades de los vehículos de clase, es decir, todos se utilizan para el transporte.

Podemos representar esta relación en código con la ayuda de la herencia.

Otra cosa intrigante sobre la herencia es que es de naturaleza transitiva. Pero ¿qué significa esto? Veremos en detalle más adelante en este artículo. Python también admite 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. Lea también el primer artículo:

Tabla de contenido

  1. ¿Qué es la herencia en la programación orientada a objetos?
  2. Diferentes formas de herencia en la programación orientada a objetos
    • Herencia única
    • Herencia múltiple
    • Herencia multinivel
    • Herencia jerárquica
    • Herencia híbrida
  3. Anulación de método
  4. La función super ()

¿Qué es la herencia en la programación orientada a objetos?

La herencia es el procedimiento en el que una clase hereda los atributos y métodos de otra clase. La clase cuyas propiedades y métodos se heredan se conoce como clase principal. Y la clase que hereda las propiedades de la clase principal es la clase Child.

Lo interesante es que, junto con las propiedades y métodos heredados, una clase hija puede tener sus propias propiedades y métodos.

Puede usar la siguiente sintaxis: para implementar la herencia en Python:

class parent_class:
body of parent class

class 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
    pass

class 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())

Producción:

a13-4443158

Hemos creado dos clases secundarias, a saber, «BMW» y «Audi», que han heredado los métodos y propiedades de la clase principal «Coche». No hemos proporcionado funciones y métodos adicionales en la clase BMW. Considerando que hay un método adicional dentro de la clase Audi.

Observe cómo los objetos de las clases secundarias pueden acceder al método de instancia description () de la clase principal con la ayuda de obj1.description () y obj2.description (). Y también se puede acceder al método separado de la clase Audi mediante obj2.audi_desc ().

Podemos verificar la clase base o padre de cualquier clase usando un atributo de clase incorporado __bases__

print(BMW.__bases__, Audi.__bases__)
screenshot-from-2020-09-28-20-44-53-3987278

Como podemos aquí, la clase base de ambas subclases es Coche. Ahora, veamos qué sucede cuando se usa __base__ con la clase padre Car:

print( Car.__bases__ )

screenshot-from-2020-09-30-17-59-33-3279774

Siempre que creamos una nueva clase en Python 3.x, se hereda de una clase básica incorporada llamada Objeto. En otras palabras, la clase Object es la raíz de todas las clases.

Formas de herencia en la programación orientada a objetos

En general, hay cinco formas de herencia basadas en la participación de las clases de padres e hijos.

1. Herencia única

Esta es una forma de herencia en la que una clase hereda solo una clase principal. 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()

screenshot-from-2020-09-28-22-11-28-7257539

Aquí, la clase Child hereda solo una clase Parent, por lo tanto, este es un ejemplo de herencia simple.

2. Herencia múltiple

Una herencia se convierte en herencias múltiples cuando una clase hereda más de una clase principal. La clase secundaria después de heredar propiedades de varias clases principales 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()

screenshot-from-2020-09-30-18-01-00-6896696

Aquí tenemos una clase Child que hereda las propiedades de las clases de tres padres Parent_1, Parent_2 y Parent_3. Todas las clases tienen funciones diferentes y todas las funciones se llaman usando el objeto de la clase Child.

Pero supongamos que una clase hija hereda dos clases que tienen la misma función:

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 Child llama a f1 (), dado que la clase Child hereda las dos clases padre, ¿qué crees que debería suceder?

obj = Child() 
obj.f1()

screenshot-from-2020-09-30-18-04-07-6132602

Pero, ¿por qué no se heredó la función f1 () de la clase Parent_2?

En herencia múltiple, la clase secundaria busca primero el método en su propia clase. Si no se encuentra, busca en las clases padre profundidad_primero y orden de izquierda a derecha. Dado que este fue un ejemplo fácil con solo dos clases principales, podemos ver claramente que la clase Parent_1 se heredó primero, por lo que la clase secundaria buscará el método en la clase Parent_1 antes de buscar en la clase Parent_2.

Pero para problemas de herencia complicados, es difícil identificar el pedido. Entonces, la forma real de hacer esto se llama Orden de resolución de método (MRO) en Python. Podemos encontrar el MRO de cualquier clase usando el atributo __mro__

Child.__mro__

screenshot-from-2020-09-30-18-06-17-7768903

Esto indica 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:
pass

class Parent_2:
pass

class Parent_3:
pass

class Child_1(Parent_1,Parent_2):
pass

class Child_2(Parent_2,Parent_3):
pass

class Child_3(Child_1,Child_2,Parent_3):
pass

Aquí, la clase Child_1 hereda dos clases: Parent_1 y Parent_2. La clase Child_2 también hereda dos clases: Parent_2 y Parent_3. Otra clase Child_3 hereda tres clases: Child_1, Child_2 y Parent_3.

Ahora, con solo mirar esta herencia, es bastante difícil determinar el orden de resolución del método para la clase Child_3. Así que aquí está el uso real de __mro __-

screenshot-from-2020-09-30-18-07-32-8298006

Podemos ver que primero, el intérprete busca Child_3, luego 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 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()

screenshot-from-2020-09-30-18-09-52-9849898

Aquí, la clase Child_1 hereda la clase Parent y la clase Child_2 hereda la clase Child_1. En este 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 () usando el objeto de la clase Class_1, se producirá un error que indica:

El objeto ‘Child_1’ no tiene atributo ‘f3’

obj_1.f3()

screenshot-from-2020-09-30-18-11-09-7482388

4- Herencia jerárquica

En esto, varias clases Child heredan una sola 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 la clase Coche.

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()

screenshot-from-2020-09-30-18-13-09-8467252

Aquí dos clases secundarias heredan la misma clase principal. La clase Child_1 tiene acceso a las funciones f1 () de la clase Parent y a la función f2 () de sí misma. Mientras que la clase Child_2 tiene acceso a las funciones f1 () de la clase Parent y 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. Será 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()

screenshot-from-2020-09-30-18-14-15-1161987

En este ejemplo, dos clases ‘Child_1 ′ y’ Child_2 ‘se derivan de la clase base’ Parent ‘usando herencia jerárquica. Otra clase ‘Child_3’ se deriva de las clases ‘Child_1’ y ‘Child_2’ usando múltiples herencias. La clase ‘Child_3’ ahora se deriva mediante herencia híbrida.

Anulación de método

El concepto de anulación es muy importante en herencia. Otorga la habilidad especial al niño / 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()

screenshot-from-2020-09-30-18-15-01-1521849

Aquí, la función f1 () de la clase Child ha anulado la función f1 () de la clase Parent. Siempre que el objeto de la clase Child invoque f1 (), se ejecuta la función de la clase Child. Sin embargo, el objeto de la clase principal puede invocar la función f1 () de la clase principal.

obj_2 = Parent()
obj_2.f1()

screenshot-from-2020-09-30-18-15-39-2868996

La función super ()

La función super () en Python devuelve un objeto proxy que hace referencia a la clase padre usando el súper palabra clave. Esta palabra clave super () es básicamente útil para acceder a los métodos anulados de la clase principal.

  1. En una jerarquía de clases con herencia única, súper ayuda a hacer referencia a las clases principales sin nombrarlas explícitamente, lo que hace que el código sea más fácil de mantener.

    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()

    screenshot-from-2020-09-30-18-16-53-9118408

    Aquí, con la ayuda de super (). F1 (), se ha llamado al método f1 () de la superclase de la clase Child, es decir, la clase Parent sin nombrarla 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 como

    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()

    screenshot-from-2020-09-30-18-16-53-9118408

    El primer parámetro se refiere a la subclase Niño, mientras que el segundo parámetro se refiere al objeto de Child que, en este caso, es uno mismo. Puede ver que el resultado después de usar super () y super (Child, self) es el mismo 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')
    screenshot-from-2020-09-30-18-18-16-3947931

    Lo que hemos hecho aquí es que llamamos 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, se ha pasado como «ChildName». Entonces, después de crear el objeto de la clase Child, primero se ejecutó la función __init__ de la clase Child, y luego la función __init__ de la clase Parent.

  2. El segundo caso de uso es admitir herencias múltiples cooperativas en un entorno de ejecución dinámica.
    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()

    screenshot-from-2020-09-30-18-19-20-4144770
    La llamada super () encuentra el siguiente método en el MRO en cada paso, por lo que First y Second también deben tenerlo, de lo contrario, la ejecución se detiene al final de primero () .__ init__.

    Tenga en cuenta que la superclase de Primera y Segunda es Objeto.

    Busquemos el MRO de Third () también-

    Third.__mro__

    screenshot-from-2020-09-30-18-19-53-1265153

    El orden es Tercero> Segundo> Primero y el mismo es el orden de nuestra salida.

Notas finales

Para concluir, en este artículo he desarrollado 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 la anulación de métodos y la función super ().

Espero que haya entendido los conceptos explicados en este artículo. Hágame saber en los comentarios a continuación si tiene alguna pregunta.

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.