Herencia y polimorfismo

Herencia y polimorfismo: este es un concepto muy importante en Python. Debes entenderlo mejor si quieres aprender.

Herencia

Una de las principales ventajas de la programación orientada a objetos es la reutilización. La herencia es uno de los mecanismos para lograr lo mismo. La herencia permite al programador crear una clase general o base primero y luego extenderla a una clase más especializada. Permite al programador escribir mejor código.

Al usar la herencia, puede usar o heredar todos los campos de datos y métodos disponibles en su clase base. Más tarde, puede agregar sus propios métodos y campos de datos, por lo que la herencia proporciona una forma de organizar el código, en lugar de reescribirlo desde cero.

En terminología orientada a objetos, cuando la clase X extiende la clase Y, entonces Y se llama clase super / padre / base y X se llama subclase / hijo / clase derivada. Un punto a tener en cuenta aquí es que solo los campos de datos y los métodos que no son privados son accesibles para las clases secundarias. Los campos y métodos de datos privados son accesibles solo dentro de la clase.

la sintaxis para crear una clase derivada es -

class BaseClass:
   Body of base class
class DerivedClass(BaseClass):
   Body of derived class

Heredar atributos

Ahora mira el siguiente ejemplo:

Salida

Primero creamos una clase llamada Date y pasamos el objeto como un argumento, here-object es una clase incorporada proporcionada por Python. Más tarde creamos otra clase llamada time y llamamos a la clase Date como argumento. A través de esta llamada, obtenemos acceso a todos los datos y atributos de la clase Date en la clase Time. Por eso, cuando intentamos obtener el método get_date del objeto de clase Time tm que creamos antes, es posible.

Jerarquía de búsqueda de atributos de objeto

  • La instancia
  • La clase
  • Cualquier clase de la que hereda esta clase

Ejemplos de herencia

Echemos un vistazo al cierre del ejemplo de herencia:

Creemos un par de clases para participar en ejemplos:

  • Animal - Clase simula un animal
  • Gato - Subclase de Animal
  • Perro - Subclase de Animal

En Python, constructor de la clase que se usa para crear un objeto (instancia) y asignar el valor a los atributos.

El constructor de subclases siempre se llama a un constructor de la clase principal para inicializar el valor de los atributos en la clase principal, luego comienza a asignar valor a sus atributos.

Salida

En el ejemplo anterior, vemos los atributos o métodos de comando que colocamos en la clase principal para que todas las subclases o clases secundarias hereden esa propiedad de la clase principal.

Si una subclase intenta heredar métodos o datos de otra subclase, se producirá un error, como vemos cuando la clase Dog intenta llamar a los métodos swatstring () de esa clase cat, arroja un error (como AttributeError en nuestro caso).

Polimorfismo ("MUCHAS FORMAS")

El polimorfismo es una característica importante de la definición de clase en Python que se utiliza cuando tiene métodos comúnmente nombrados en clases o subclases. Esto permite que las funciones utilicen entidades de diferentes tipos en diferentes momentos. Por lo tanto, proporciona flexibilidad y un acoplamiento flexible para que el código se pueda ampliar y mantener fácilmente con el tiempo.

Esto permite que las funciones utilicen objetos de cualquiera de estas clases polimórficas sin necesidad de conocer las distinciones entre las clases.

El polimorfismo se puede llevar a cabo a través de la herencia, con subclases haciendo uso de métodos de clase base o anulándolos.

Comprendamos el concepto de polimorfismo con nuestro ejemplo de herencia anterior y agreguemos un método común llamado show_affection en ambas subclases:

En el ejemplo que podemos ver, se refiere a un diseño en el que un objeto de tipo diferente se puede tratar de la misma manera o más específicamente dos o más clases con el método del mismo nombre o interfaz común porque el mismo método (show_affection en el ejemplo siguiente) se llama con cualquier tipo de objeto.

Salida

Entonces, todos los animales muestran afecto (show_affection), pero lo hacen de manera diferente. Los comportamientos "show_affection" son, por tanto, polimórficos en el sentido de que actúan de forma diferente según el animal. Entonces, el concepto abstracto de "animal" no significa realmente "mostrar_afecto", pero animales específicos (como perros y gatos) tienen una implementación concreta de la acción "mostrar_afecto".

Python en sí tiene clases que son polimórficas. Por ejemplo, la función len () se puede utilizar con varios objetos y todos devuelven la salida correcta según el parámetro de entrada.

Primordial

En Python, cuando una subclase contiene un método que anula un método de la superclase, también puede llamar al método de la superclase llamando

Super (subclase, self) .method en lugar de self.method.

Ejemplo

class Thought(object):
   def __init__(self):
      pass
   def message(self):
      print("Thought, always come and go")

class Advice(Thought):
   def __init__(self):
      super(Advice, self).__init__()
   def message(self):
      print('Warning: Risk is always involved when you are dealing with market!')

Heredar el constructor

Si vemos en nuestro ejemplo de herencia anterior, __init__ estaba ubicado en la clase principal en la parte superior porque la clase secundaria perro o gato no tenía el método __init__. Python usó la búsqueda de atributos de herencia para encontrar __init__ en la clase animal. Cuando creamos la clase secundaria, primero buscará el método __init__ en la clase dog, luego no lo encontró, luego buscó en la clase padre Animal y lo encontró allí y lo llamó allí. Entonces, a medida que nuestro diseño de clase se vuelve complejo, es posible que deseemos inicializar una instancia procesándola primero a través del constructor de la clase principal y luego a través del constructor de la clase secundaria.

Salida

En el ejemplo anterior, todos los animales tienen un nombre y todos los perros una raza en particular. Llamamos al constructor de la clase padre con super. Entonces el perro tiene su propio __init__ pero lo primero que pasa es que lo llamamos super. Super tiene una función incorporada y está diseñado para relacionar una clase con su superclase o su clase padre.

En este caso decimos que obtenga la superclase de perro y pase la instancia de perro a cualquier método que digamos aquí el constructor __init__. En otras palabras, estamos llamando a la clase padre Animal __init__ con el objeto perro. Puede preguntar por qué no solo diremos Animal __init__ con la instancia del perro, podríamos hacer esto, pero si el nombre de la clase animal cambiara, en algún momento en el futuro. ¿Qué pasa si queremos reorganizar la jerarquía de clases, por lo que el perro heredó de otra clase? El uso de super en este caso nos permite mantener las cosas modulares y fáciles de cambiar y mantener.

Entonces, en este ejemplo, podemos combinar la funcionalidad general __init__ con una funcionalidad más específica. Esto nos da la oportunidad de separar la funcionalidad común de la funcionalidad específica que puede eliminar la duplicación de código y relacionar la clase entre sí de una manera que refleje el diseño general del sistema.

Conclusión

  • __init__ es como cualquier otro método; se puede heredar

  • Si una clase no tiene un constructor __init__, Python verificará su clase padre para ver si puede encontrar uno.

  • Tan pronto como encuentra uno, Python lo llama y deja de buscar

  • Podemos usar la función super () para llamar a métodos en la clase padre.

  • Es posible que queramos inicializar en el padre así como en nuestra propia clase.

Herencia múltiple y árbol de búsqueda

Como su nombre indica, la herencia múltiple en Python es cuando una clase hereda de varias clases.

Por ejemplo, un niño hereda rasgos de personalidad de ambos padres (madre y padre).

Sintaxis de herencia múltiple de Python

Para hacer que una clase herede de varias clases principales, escribimos los nombres de estas clases dentro del paréntesis en la clase derivada mientras la definimos. Separamos estos nombres con una coma.

A continuación se muestra un ejemplo de eso:

>>> class Mother:
   pass

>>> class Father:
   pass

>>> class Child(Mother, Father):
   pass

>>> issubclass(Child, Mother) and issubclass(Child, Father)
True

La herencia múltiple se refiere a la capacidad de heredar de dos o más de dos clases. La complejidad surge cuando el niño hereda de los padres y los padres heredan de la clase de los abuelos. Python trepa a un árbol de herencia en busca de atributos que se solicitan leer de un objeto. Verificará en la instancia, dentro de la clase, luego en la clase principal y, por último, en la clase de los abuelos. Ahora surge la pregunta en qué orden se buscarán las clases: primero la respiración o primero la profundidad. Por defecto, Python va con la profundidad primero.

Es por eso que en el siguiente diagrama, Python busca el método dothis () primero en la clase A. Por lo tanto, el orden de resolución del método en el siguiente ejemplo será

Mro- D→B→A→C

Mire el siguiente diagrama de herencia múltiple:

Veamos un ejemplo para comprender la función "mro" de un Python.

Salida

Ejemplo 3

Tomemos otro ejemplo de herencia múltiple con "forma de diamante".

El diagrama anterior se considerará ambiguo. De nuestro ejemplo anterior entendiendo el “orden de resolución del método”, es decir, mro será D → B → A → C → A pero no lo es. Al obtener la segunda A de la C, Python ignorará la A anterior, por lo que el mro en este caso será D → B → C → A.

Creemos un ejemplo basado en el diagrama anterior:

Salida

Una regla simple para entender el resultado anterior es: si la misma clase aparece en el orden de resolución del método, las apariciones anteriores de esta clase se eliminarán del orden de resolución del método.

En conclusión

  • Cualquier clase puede heredar de varias clases

  • Python normalmente usa un orden de "profundidad primero" cuando busca clases heredadas.

  • Pero cuando dos clases heredan de la misma clase, Python elimina las primeras apariciones de esa clase del mro.

Decoradores, métodos estáticos y de clase

Las funciones (o métodos) se crean mediante la declaración def.

Aunque los métodos funcionan exactamente de la misma manera que una función, excepto en un punto donde el primer argumento del método es un objeto de instancia.

Podemos clasificar los métodos en función de cómo se comportan, como

  • Simple method- definido fuera de una clase. Esta función puede acceder a los atributos de la clase alimentando el argumento de la instancia:

def outside_func(():
  • Instance method -

def func(self,)
  • Class method - si necesitamos usar atributos de clase

@classmethod
def cfunc(cls,)
  • Static method - no tengo ninguna información sobre la clase

@staticmethod
def sfoo()

Hasta ahora que hemos visto el método de instancia, ahora es el momento de obtener una idea de los otros dos métodos,

Método de clase

El decorador @classmethod es un decorador de funciones incorporado que se pasa la clase en la que se llamó o la clase de la instancia en la que se llamó como primer argumento. El resultado de esa evaluación ensombrece la definición de su función.

sintaxis

class C(object):
   @classmethod
   def fun(cls, arg1, arg2, ...):
      ....
fun: function that needs to be converted into a class method
returns: a class method for function

Tienen acceso a este argumento cls, no puede modificar el estado de la instancia del objeto. Eso requeriría acceso a uno mismo.

  • Está vinculado a la clase y no al objeto de la clase.

  • Los métodos de clase aún pueden modificar el estado de la clase que se aplica a todas las instancias de la clase.

Método estático

Un método estático no toma un parámetro self ni cls (clase) pero es libre de aceptar un número arbitrario de otros parámetros.

syntax

class C(object):
   @staticmethod
   def fun(arg1, arg2, ...):
   ...
returns: a static method for function funself.
  • Un método estático no puede modificar el estado del objeto ni el estado de la clase.
  • Están restringidos en cuanto a los datos a los que pueden acceder.

Cuando usar que

  • Generalmente usamos el método de clase para crear métodos de fábrica. Los métodos de fábrica devuelven un objeto de clase (similar a un constructor) para diferentes casos de uso.

  • Generalmente usamos métodos estáticos para crear funciones de utilidad.