14. POO - manuelmarinduque/Apuntes_Python_3 GitHub Wiki

Programación orientada a objetos en Python

Tomado de POO I. Vídeo 24 Fuente de complemento

  • Consiste en trasladar la naturaleza de los objetos de la vida real al código de programación.

  • La naturaleza de un objeto de la vida real está en que tiene unos atributos o propiedades y un comportamiento o acciones (métodos) que definen su estado; esto depende del contexto que se trabaja.

Terminología

  • Contexto: Abstracción del mundo real y la lógica del negocio basada en los requerimientos del software.

  • Características: Atributos y comportamientos que definen su estado.

Clase

Modelo o plantilla base, donde se redactan las características comunes que comparten un grupo de objetos para su creación. El nombre de una clase se escribe en notación CamelCase, es decir, primer nombre de cada palabra en mayúscula:

class NombreClase(): # CamelCase
    # Características de la clase
        # Atributos
        # Comportamientos (métodos)

Objeto

Ejemplar o instancia de una clase con atributos definidos que lo distinguen de otros. Para crear un objeto de una clase se llama a la clase por su propio nombre pasando los argumentos que requiera su método constructor __init__, si existe.

nombre_objeto = NombreClase(arg1, arg2,..., argn)

Un objeto se almacena en la memoria RAM del computador y existe únicamente cuando el programa está en ejecución. Al finalizar o cerrarse, el objeto deja de existir y su información (sus características) desaparecen. Si tal información se guarda en una base de datos, al finalizar el programa no desaparece, a esto se le conoce como persistencia de datos.

En Python, cualquier elemento del lenguaje como una variable, función, módulo, estructura de datos, una instancia de clase, etc. es un objeto perteneciente a una clase. Con la siguiente función y el atributo especial: type(objeto).__name__ se conoce el nombre de la clase a la que pertenece tal objeto.

Si una función o método recibe como parámetro un objeto, este se pasa por referencia en memoria, es decir, se opera con el objeto original (Ver archivo 4. Funciones)

No se puede crear copias de los objetos de la forma: copia_objeto = objeto_original. Si se modifican los atributos del objeto copia, también se modifican los del original. Lo anterior debido a que ambos apuntan o hacen referencia a la misma posición en memoria. Para copiar objetos se importa el módulo copy y se usa su método copy(objeto-a-copiar).

Modularización

Programa conformado por varias módulos o dividido en pequeñas partes (ver archivo 17. Módulos y paquetes para definición), con un límite definido, independientes y reutilizables, que trabajan en conjunto para llevar a cabo una serie de tareas comunes. Si un módulo falla, no afecta al resto del programa.

Método

Es una función perteneciente a una clase y define el comportamiento de sus objetos. Por defecto recibe el parámetro self, que hace referencia al objeto que llamó al método y también precede a los atributos dentro del método:

def nombre_metodo(self, parametros):
    self.atributo_1 = parametro_1
    self.atributo_2 = parametro_2
    self.atributo_n = parametro_n

Nota: Los parámetros que recibe el método deben asignarse a los respectivos atributos de clase para poder internamente operar con ellos.

Notación del punto

Tanto para acceder a los atributos como para llamar a los métodos se utiliza el método denominado notación de punto que se basa en escribir el nombre del objeto o de la clase seguido de un punto y el nombre del atributo o del método con los argumentos requeridos:

clase.atributo
objeto.atributo
objeto.metodo(argumentos).

Métodos y atributos especiales de una clase

Métodos especiales

Fuente

  • __init__(self, args): Método constructor que se ejecuta al crear un objeto. Se explica más adelante.

  • __del__(self): Método destructor que se ejecuta al suprimir un objeto.

  • __str__(self): Método que se ejecuta cuando se imprime por pantalla un objeto: print(objeto). Se utiliza mayormente para imprimir la descripción de un objeto. Ver enlace

Atributos especiales

Fuente

Se utilizan mediante la notación del punto:

  • __name__: Devuelve el nombre de una función, clase o módulo.

  • __doc__: Devuelve la cadena de documentación de la clase, función o módulo; o None si no se ha definido.

  • __module__: Devuelve el nombre del módulo donde se encuentra un elemento.

  • __dict__: Devuelve un diccionario que contiene el espacio de nombres de la clase (o de un objeto instanciado).

Constructor de clase

Es un método especial que se ejecuta al momento de crear un objeto de la clase. Su propósito es dar valores a los atributos de la clase, definiendo así un objeto, y reservar memoria para tal objeto:

  • Constructor sin parámetros: Asigna explícitamente valores a los atributos y cada objeto que se cree tendrá dichos valores por defecto.
def __init__(self):
    # Atributos precedidos por _self._
    self.atributo_1 = valor
    self.atributo_2 = valor
    self.atributo_3 = valor
    self.atributo_n = valor
  • Constructor con parámetros: Recibe un conjunto de parámetros al crear un objeto, los cuales inicializan sus atributos:
def __init__(self, *parametros):
    self.atributo_1 = parametro_1
    self.atributo_2 = parametro_2
    self.atributo_3 = parametro_3
    self.atributo_n = parametro_n

Nota: Las características de ambos constructores se pueden mezclar.

Encapsulación

Ocultar y proteger del exterior el funcionamiento interno de una clase, evitando su manipulación:

  • Encapsulación de atributos: Consiste en proteger y ocultar un atributo de una clase para no se pueda acceder y modificar desde fuera, sólo desde dentro de la clase a través de sus métodos.

  • Encapsulación de métodos: Similar a la de atributos, oculta y protege el método para que no se tenga acceso desde fuera de la clase. Un uso es el de asegurar que una secuencia de funciones se cumpla, en el que una función inicia la secuencia, llamando a otras funciones encapsuladas.

Sintaxis: Precediendo el nombre del atributo o método por dos guiones bajos siempre que se escriba.

Nota: Saber qué atributos y métodos encapsular depende del contexto que se trabaja.

Herencia

Se basa en el principio de sustitución "es un/a": "la subclase es un/a superclase" (más no a la inversa).

En una jerarquía de clases, que cumple el principio de sustitución, la herencia es el paso de atributos y métodos (incluído el constructor) a través de los niveles de la jerarquía. La clase que pasa sus características a otras se llama clase padre o super clase y la que los recibe se llama subclase, quedando con sus propias características y con las nuevas heredadas, podiendo sobreescribirlas. La herencia sirve para reutilizar código en caso de crear clases similares.

En Python todas las clases son subclases de la superclase Object

Generalización vs especialización: En la cima de la jerarquía está la clase con las características comunes más generales descendiendo a clases con características únicas y específicas que las diferencian.

Sobreescritura de métodos: Modificar o personalizar en la subclase un método heredado para adaptarlo a sí misma.

Constructores en una subclase: Si una subclase no define su método constructor, hereda el de su padre y, por tanto, si este recibe parámetros, deben especificarse al crear un objeto de la subclase. En caso de que la subclase defina su constructor, dentro de este debe llamarse al constructor de su padre con la función super(), indicando en dicho constructor padre sus parámetros, en caso de recibir. De igual modo, estos mismos parámetros deben ser recibidos por el constructor hijo. Así, a través del método super() se inicializan los atributos de la clase padre. De lo contrario, la subclase produce error si usa las características de la clase padre (Ver ejemplo abajo).

Sintaxis herencia normal y herencia múltiple

Escribir las clases a las cuales heredar sus características entre los paréntesis de la definición de clase:

def NombreClase(clase_heredada_1, clase_heredada_2, clase_heredada_n):
    # Cuerpo de la clase

Leer Orden de Resolución de Métodos (MRO) y la función super()

Herencia múltiple

Si las clases padres tienen métodos y atributos con nombre en común, como el constructor, sólo se usan los de la primer clase definida a la izquierda, ignorando el resto. Por otro lado, los métodos y atributos no comunes se heredan sin ningún problema. Es importante nombrar adecuadamente los atributos y los métodos en cada clase para no crear conflictos.

La herencia múltiple se torna compleja en un programa con una jerarquía extensa.

La función super()

Llama a los atributos y los métodos de la clase padre. En herencia múltiple, tiene en cuenta el orden de resolución de métodos (MRO), por lo que sólo llama a las características de la clase padre definida a la izquierda: (Ver ejemplo en el link de arriba)

super().nombre_metodo_padre(parametros)

Constructores en la herencia múltiple

En el caso de los constructores de las demás clases padres, no se usa super() si no que la propia clase padre llama a su constructor __init__ pasándole los parámetros necesarios en caso de tenerlos, incluyendo siempre self. También la clase padre principal puede emplear este método, omitiendo la función super() en el código.

Ejemplo

Con super() se logra no tener que escribir todo el método descripción() de la clase padre:

class Persona():

    def __init__(self, nombre, edad, direccion):
        self.nombre=nombre
        self.edad=edad
        self.direccion=direccion

    def descripcion(self):
        print("Nombre: ", self.nombre, " edad: " , self.edad, " Direccion: ", self.direccion)


class Empleado(Persona): # Herencia

    def __init__(self, salario, antiguedad, nombre, edad, direccion):
        super().__init__(nombre, edad, direccion)
        self.salario=salario
        self.antiguedad=antiguedad

    # Sobreescritura del método descripción definido en la clase padre:
    def descripcion(self):
        # Se llama el método descripción() del padre:
        super().descripcion()
        print(" Salario: ", self.salario, " Antiguedad: ", self.antiguedad)

# Creación de un objeto persona:

Manuel = Empleado(210, 55, "Manuel", 22, "Tulua")
Manuel.descripcion()

Funciones issubclass() y isinstance()

La función issubclass(SubClase, ClaseSup) se utiliza para comprobar si una clase (SubClase) es hija de otra superior (ClaseSup), devolviendo True o False según sea el caso.

La función isinstance(Objeto, Clase) se utiliza para comprobar si un objeto pertenece a una clase o clase superior, devolviendo True o False según sea el caso.

Polimorfismo

Propiedad de la herencia en que objetos de distintas subclases pueden responder a una misma acción. Debido a esto, los objetos de clases que comparten únicamente el nombre de un atributo o método pueden almacenarse en una estructura iterable y/o ser el argumento a un parámetro de una función y manipular con este argumento dicho atributo o método.

⚠️ **GitHub.com Fallback** ⚠️