Herencia en Python – Simple, Múltiple y Multinivel

Avatar Tutor | octubre 28, 2018

La herencia es una característica poderosa en la programación orientada a objetos.

Se refiere a definir una nueva clase con poca o ninguna modificación a una clase existente. La nueva clase se denomina clase derivada(o secundaria) y la clase de la que se hereda se denomina clase base(o principal).

Sintaxis de herencia de Python

class BaseClass:
  Cuerpo de la clase Base
class DerivedClass(BaseClass):
  Cuerpo de la clase Derivada

La clase derivada hereda características de la clase base, agregándole nuevas características. Esto resulta en la reutilización del código.

Ejemplo de herencia en Python

Para demostrar el uso de la herencia, tomemos un ejemplo.

Un polígono es una figura cerrada con 3 o más lados. Digamos que tenemos una clase llamada Polígono definida de la siguiente manera.

class Polygon:
    def __init__(self, no_of_sides):
        self.n = no_of_sides
        self.sides = [0 for i in range(no_of_sides)]

    def inputSides(self):
        self.sides = [float(input("Enter side "+str(i+1)+" : ")) for i in range(self.n)]

    def dispSides(self):
        for i in range(self.n):
            print("Side",i+1,"is",self.sides[i])

Esta clase tiene atributos de datos para almacenar el número de lados, y la magnitud n de cada lado como una lista, lados.

El método inputSides() toma la magnitud de cada lado y, de manera similar, dispSides() los mostrará correctamente.

Un triángulo es un polígono con 3 lados. Entonces, podemos crear una clase llamada Triángulo que se hereda de Polígono. Esto hace que todos los atributos disponibles en la clase Polígono estén disponibles en Triángulo. No necesitamos definirlos de nuevo(reutilización del código). Triángulo se define de la siguiente manera.

class Triangle(Polygon):
    def __init__(self):
        Polygon.__init__(self,3)

    def findArea(self):
        a, b, c = self.sides
        # calculate the semi-perimeter
        s =(a + b + c) / 2
        area =(s*(s-a)*(s-b)*(s-c)) ** 0.5
        print('El area del triangulo es %0.2f' %area)

Sin embargo, la clase Triangle tiene un nuevo método findArea() para encontrar e imprimir el área del triángulo. Aquí hay una muestra de su ejecución.

>>> t = Triangle()

>>> t.inputSides()
Enter side 1 : 3
Enter side 2 : 5
Enter side 3 : 4

>>> t.dispSides()
Side 1 is 3.0
Side 2 is 5.0
Side 3 is 4.0

>>> t.findArea()
El area del triangulo es 6.00

Podemos ver que, aunque no definimos métodos como inputSides() o dispSides() para la clase Triangle, pudimos usarlos.

Si no se encuentra un atributo en la clase, la búsqueda continúa a la clase base. Esto se repite recursivamente, si la clase base se deriva de otras clases.

Method Overriding en Python

En el ejemplo anterior, observe que el método __init __() se definió en ambas clases, Triángulo y Polígono. Cuando esto sucede, el método en la clase derivada anula eso en la clase base. Es decir, __init __() en Triangle obtiene preferencia sobre Polygon.

Generalmente cuando se reemplaza un método base, tendemos a extender la definición en lugar de simplemente reemplazarla. Lo mismo se está haciendo llamando al método en la clase base desde el de la clase derivada(llamando a Polygon .__ init __() desde __init __() en Triángulo).

Una mejor opción sería utilizar la función incorporada super(). Entonces, super() .__ init __(3) es equivalente a Polygon .__ init __(self, 3) y se prefiere.

Dos funciones incorporadas isinstance() y issubclass() se usan para verificar las herencias. La función isinstance() devuelve True si el objeto es una instancia de la clase u otras clases derivadas de ella. Cada clase en Python hereda del objeto de clase base.

>>> isinstance(t,Triangle)
True

>>> isinstance(t,Polygon)
True

>>> isinstance(t,int)
False

>>> isinstance(t,object)
True

De manera similar, issubclass() se usa para verificar la herencia de clase.

>>> issubclass(Polygon,Triangle)
False

>>> issubclass(Triangle,Polygon)
True

>>> issubclass(bool,int)
True

Herencia Múltiple En Python

Al igual que C ++, una clase puede derivarse de más de una clase base en Python. Esto se llama herencia múltiple.

En la herencia múltiple, las características de todas las clases base se heredan en la clase derivada. La sintaxis para la herencia múltiple es similar a la herencia simple.

Ejemplo

class Base1:
    pass

class Base2:
    pass

class MultiDerived(Base1, Base2):
    pass

Aquí, MultiDerived se deriva de las clases Base1 y Base2.

Herencia multinivel en Python

Por otro lado, también podemos heredar de una clase derivada. Esto se llama herencia multinivel. Puede ser de cualquier profundidad en Python.

En la herencia multinivel, las características de la clase base y la clase derivada se heredan en la nueva clase derivada.

A continuación se muestra un ejemplo con la visualización correspondiente.

class Base:
    pass

class Derived1(Base):
    pass

class Derived2(Derived1):
    pass

Aquí, Derived1 se deriva de Base, y Derived2 se deriva de Derived1.

Método de resolución de orden en Python

Cada clase en Python se deriva del objeto de clase. Es el tipo más básico en Python.

Así que técnicamente, todas las demás clases, ya sean integradas o definidas por el usuario, son clases derivadas y todos los objetos son instancias de la clase de objeto.

# Output: True
print(issubclass(list,object))

# Output: True
print(isinstance(5.5,object))

# Output: True
print(isinstance("Hello",object))

En el escenario de herencia múltiple, cualquier atributo especificado se busca primero en la clase actual. Si no se encuentra, la búsqueda continúa en clases primarias en profundidad, de izquierda a derecha, sin buscar la misma clase dos veces.

Por lo tanto, en el ejemplo anterior de la clase MultiDerived el orden de búsqueda es [MultiDerived, Base1, Base2, object]. Este orden también se denomina linealización de la clase MultiDerived y el conjunto de reglas que se utiliza para encontrar este orden se denomina Orden de resolución de métodos(MRO).

El MRO debe evitar el orden local de precedencia y también proporcionar monotonicidad. Asegura que una clase siempre aparezca antes que sus padres y, en el caso de varios padres, el orden es el mismo que la tupla de clases base.

El MRO de una clase puede verse como el atributo __mro__ o el método mro(). El primero devuelve una tupla mientras que el segundo devuelve una lista.

>>> MultiDerived.__mro__
(<class '__main__.MultiDerived'>,
 <class '__main__.Base1'>,
 <class '__main__.Base2'>,
 <class 'object'>)

>>> MultiDerived.mro()
[<class '__main__.MultiDerived'>,
 <class '__main__.Base1'>,
 <class '__main__.Base2'>,
 <class 'object'>]

Aquí hay un ejemplo de herencia múltiple un poco más complejo y su visualización junto con el MRO.

class X: pass
class Y: pass
class Z: pass

class A(X,Y): pass
class B(Y,Z): pass

class M(B,A,Z): pass

# Output:
# [<class '__main__.M'>, <class '__main__.B'>,
# <class '__main__.A'>, <class '__main__.X'>,
# <class '__main__.Y'>, <class '__main__.Z'>,
# <class 'object'>]

print(M.mro())

Written by Tutor