Introducción a Clases en Python

Introducción

Python es un lenguaje de programación orientado a objetos.

Todo en Python es un objeto, con sus propiedades y métodos.

Clases

Podemos imaginarnos una clase como una plantilla o un plano para construir objetos.

Para crear una clase, usa el la palabra clave class:

Supongamos, por ejemplo, que queremos crear una plataforma para recolectar toda la información personal que podamos de nuestros usuarios (nada parecido con la realidad) porque… sí.

Creemos una clase que no haga nada.

class Usuario:
    pass

La razón de pass es debido a que una clase necesita al menos una línea para poder existir.

Consejo

Para crear los nombres de las clases, comience con mayúsculas.

Ok. Ahora creemos un usuario:

u1=Usuario()
u2=Usuario()
u1
<__main__.Usuario at 0x7f95903f9ac0>

Como podemos ver, parece que estuvieramos llamando un método (o función), y en efecto es algo parecido

u1 es una instancia de la clase Usuario.

También podemos llamar a u1 un objeto.

Podemos adjuntar datos a este objeto, usamos la notación punto:

#Adjuntando datos a el objeto u1 de la clase Usuario

u1.nombre="Aprendizaje"
u1.apellido="Profundo"
u2.edad =15

print(u1.nombre)
print(u1.apellido)
print(u2.edad)
Aprendizaje
Profundo
15

Los datos adjuntados a un objeto se les llaman atributos

OJO

Nombre y apellido no son variables existentes en el ambiente de trabajo.

Consejo

Se recomienda usar minúsculas para los nombres de los campo (Tradición Pythonica).

Es común diferenciar entre atributos de clase y atributos de instancia de clase. Por lo pronto nos referimos a los atributos de instancia de clase qu refieren a la información incluida en una instancia de clase.

u1.nombre
'Aprendizaje'

Una bonita consecuencia, es que podemos crear muchos objetos con campos del mismo nombre sin tener que definir una variable diferente para cada dato adjuntado del objeto.

Hagamos otro objeto:

u2=Usuario()
u2.nombre="Francisco"
u2.apellido="Talavera"
u2.edad=34
#edad de u2
u2.edad
34
# u1 no tiene edad asginada
u1.edad
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/var/folders/w7/ws1ycc_x4ldf54pl86v3j8xr0000gn/T/ipykernel_79189/456201229.py in <module>
      1 # u1 no tiene edad asginada
----> 2 u1.edad

AttributeError: 'Usuario' object has no attribute 'edad'

Se estarán preguntando…

¿Para qué tomarnos la molestia si pudimos hacer todo un diccionario?

La respuesta la encontraremos en características adicionales de las clases. Estas contienen:

  • Métodos

  • Inicialización

La función __init__()

Una función dentro de una clase de llama método.

__init__ es el abreviado de initialization (inicialización).

También se le conoce como el constructor.

Note los dos guiones bajos antes y despues de init.

class Usuario:
    def __init__(self, nombre_completo, cumple):
        self.nombre = nombre_completo
        self.cumple = cumple

u3 = Usuario("Thomas Anderson", '19620311')

print(u3.nombre)
print(u3.cumple)

Nota

self es el parámetro que referencia la instancia actual de la clase y se usa para acceder a los atributos de dicha clase. No tiene que llamarse self.

class Usuario:
    def __init__(mi_objeto, nombre_completo, cumple):
        mi_objeto.nombre = nombre_completo
        mi_objeto.cumple = cumple

u3 = Usuario("Thomas Anderson", '19620311')

print(u3.nombre)
print(u3.cumple)

Agreguemos otra característica más.

Por ejemplo, extraer nombre y apellido:

class Usuario:
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        
        #Extraer partes
        piezas_nombre=nombre_completo.split(" ")
        self.nombre=piezas_nombre[0]
        self.apellido=piezas_nombre[-1]
        
u = Usuario("Thomas Anderson", '19620311')

print(u.nombre_c)
print(u.nombre)
print(u.apellido)
print(u.cumple)
class Usuario:
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        
        #Extraer partes
        piezas_nombre=nombre_completo.split(" ")
        self.nombre=piezas_nombre[0]
        self.apellido=piezas_nombre[-1]
        
Neo = Usuario("Thomas Anderson", '19620311')

print(Neo.nombre_c)
print(Neo.nombre)
print(Neo.apellido)
print(Neo.cumple)

Documentación de una clase

Podemos documentar la clase de la siguiente manera:

class Usuario:
    """Un usuario de nuestra plataforma. Por ahora
    sólo recolectamos nombre y cumpleaños.
    Pero pronto tendremos mucho más que eso."""
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        
        #Extraer partes
        piezas_nombre=nombre_completo.split(" ")
        self.nombre=piezas_nombre[0]
        self.apellido=piezas_nombre[-1]
        
help(Usuario)

Agregando métodos a una clase

Es posible crear métodos propios a una clase.

Creemos por ejemplo un método que extraiga la edad de nuestro usuario.

import datetime

class Usuario:
    """Un usuario de nuestra plataforma. Por ahora
    sólo recolectamos nombre y cumpleaños.
    Pero pronto tendremos mucho más que eso."""
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        
        #Extraer partes
        piezas_nombre=nombre_completo.split(" ")
        self.nombre=piezas_nombre[0]
        self.apellido=piezas_nombre[-1]
        
    def edad(self):
        """Regresa la edad de nuestro usuario en años."""
        hoy=datetime.date.today()
        año=int(self.cumple[0:4])
        mes=int(self.cumple[4:6])
        dia=int(self.cumple[6:8])
        fecha_cumple=datetime.date(año,mes,dia)
        edad_dias=(hoy-fecha_cumple).days
        edad_años=edad_dias/365
        return int(edad_años)
    
Neo=Usuario("Thomas Anderson","19620311")

print("Neo tiene",Neo.edad(),"años")
help(Usuario)

Imprimiendo contenidos de una clase por defecto

Para establecer una forma de print por defecto en una clase, puede agregar el método reservado __str__. Vamos a redefinir nuestra clase para incluirlo.

import datetime

class Usuario:
    """Un usuario de nuestra plataforma. Por ahora
    sólo recolectamos nombre y cumpleaños.
    Pero pronto tendremos mucho más que eso."""
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        
        #Extraer partes
        piezas_nombre=nombre_completo.split(" ")
        self.nombre=piezas_nombre[0]
        self.apellido=piezas_nombre[-1]
        
    def edad(self):
        """Regresa la edad de nuestro usuario en años."""
        hoy=datetime.date.today()
        año=int(self.cumple[0:4])
        mes=int(self.cumple[4:6])
        dia=int(self.cumple[6:8])
        fecha_cumple=datetime.date(año,mes,dia)
        edad_dias=(hoy-fecha_cumple).days
        edad_años=edad_dias/365
        return int(edad_años)
     
    def __str__(self):
        return self.nombre_c + '  tiene ' + str(self.edad()) + ' años'
Neo=Usuario("Thomas Anderson","19620311")
print(Neo)

Ejecución de una tarea por defecto en una instancia de clase: call

Puede usar el método reservado __call__ para ejecutar uan tarea por defecto cuando se llama a un objeto.

Por ejemplo supongamos que deseamos ver cuantas veces ha sido llamado un objeto.

Agrandamos una vez más nuestra clase Usuario.

import datetime

class Usuario:
    """Un usuario de nuestra plataforma. Por ahora
    sólo recolectamos nombre y cumpleaños.
    Pero pronto tendremos mucho más que eso."""
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        
        self.llamadas = 0
        
        #Extraer partes
        piezas_nombre=nombre_completo.split(" ")
        self.nombre=piezas_nombre[0]
        self.apellido=piezas_nombre[-1]
        
    def edad(self):
        """Regresa la edad de nuestro usuario en años."""
        hoy=datetime.date.today()
        año=int(self.cumple[0:4])
        mes=int(self.cumple[4:6])
        dia=int(self.cumple[6:8])
        fecha_cumple=datetime.date(año,mes,dia)
        edad_dias=(hoy-fecha_cumple).days
        edad_años=edad_dias/365
        return int(edad_años)
     
    def __str__(self):
        return self.nombre_c + '  tiene ' + str(self.edad()) + ' años'
    
    def __call__(self):
        self.llamadas +=1
        return(self.llamadas)
Neo=Usuario("Thomas Anderson","19620311")
print(Neo)
print(Neo())
print(Neo())

Referencias y copia de objetos

Smith=Usuario("Agente Smith","20100515")
print(Smith)

ahora una referencia a Neo

px = Neo
print(px)
print(Neo)

Pero ahora observe lo que pasa si modifica px

px.cumple ='19500319'
print(Neo)
print(px)

Esto ocurre porque en realidad px es una referencia al objeto Neo. Si desea una copia física puede por ejemplo usar la función copy del módulo estándar copy.

from copy import copy

px = copy(Neo)
Neo.cumple = "19600311"
print(px)
print(Neo)

Eliminación de objetos: del

Se usa para eliminar un objeto. Por ejemplo:

del px
print(px)

Atributos Intrínsecos de clases y objetos

Cada clase y objeto de Python tiene una conjunto de atributos intrínsecos que pueden ser llamados.

Los atributos intrínsecos de clase son:

  • __name__: nombre de la clase

  • __module__: módulo al cual pertenece la clase

  • __bases__: clases base de ésta clase

  • __dict__: diccionario conteniendo un conjunto clave/valor con todos los atributos de la clase incluidos los métodos.

Los atributos intrínsecos de los objetos son:

  • __class__: nombre de la clase del objeto

  • __dict__: diccionario conteniendo un conjunto clave/valor con todos los atributos

print('Atributos de clase\n')
print('Nombre de la clase: ',Usuario.__name__)
print('\n Módulo: ', Usuario.__module__)
print('\n Documentación:\n',Usuario.__doc__)
print('\nDiccionario de la clase: \n',Usuario.__dict__)
print('\nClases Base: ',Usuario.__bases__)
print('\nAtributos del objeto Neo\n')
print('Clase: ',Neo.__class__)
print('\n Diccionario: ', Neo.__dict__)

Herencia de clases

Una de las grandes ventajas de usar clases en programación es poder generar clases más especializadas a partir de una o más clases base.

Esto característica permite re-utilizar código y también permite escribir un código más limpio y legible.

Supongamos que a la clase Usuario le queremos dar un tipo de publicidad en específico.

Creemos entonces una clase que hable sobre los gustos del usuario, pero referenciando a la clase ya creada.

class Lector(Usuario):
    pass
help(Lector)
l=Lector("Daniel Montenegro","19901026")
print(l.nombre_c)
print(l.nombre)
print(l.edad())
print(l)
print(l())

Al intentar colocar un constructor sobre la clase heredada, se perderá la función constructora heredada de Usuario:

class Lector(Usuario):
    def __init__(self, nombre_completo, cumple):
        self.nombre_c = nombre_completo
        self.cumple = cumple
        # Agregar cositas
l=Lector("Daniel Montenegro","19901026")
print(l.nombre_c)
print(l.edad())

Referencia a la clase base: super()

Dado que al heredar una clase de otra, estamos pensando en conservar la funcionalidad de la clase base, es importante poder usar el constructor de la clase base junto con el constructor extendido en la clase heredada. Para hacer esta característica posible se utiliza super() como se muestra a continuación. super() es un enlace o referencia a la clase base. Entonces, si deseamos usar el constructor de la clase base se puede escribir

super().__init__(…)

Veamos el uso de super() en nuestra clase Lector, la cual heredamos de la clase Usuario.

class Lector(Usuario):
    def __init__(self, nombre_completo, cumple, gustos):
        super().__init__(nombre_completo, cumple)
        self.gustos=gustos
        # Agregar otras cosas
l=Lector("Daniel Montenegro","19901026","Jack Kerouac")
print(l.nombre_c)
print(l.edad())
print(l.gustos)
print(l)

Finalmente, agreguemos un método a la clase Lector para extender su funcionalidad:

class Lector(Usuario):
    def __init__(self, nombre_completo, cumple, gustos):
        super().__init__(nombre_completo, cumple)
        self.gustos=gustos
        año=int(self.cumple[0:4])
        mes=int(self.cumple[4:6])
        dia=int(self.cumple[6:8])
        self.fecha_cumple=datetime.date(año,mes,dia)
        
    def info(self):
        print(" El Usuario",self.nombre_c,", nacido en",self.fecha_cumple, ", tiene",self.edad(),"años", "y le gustan las obras de",self.gustos)
l=Lector("Daniel Montenegro","19901026","Jack Kerouac")
l.info()

Autores

  1. Alvaro Mauricio Montenegro Díaz, ammontenegrod@unal.edu.co

  2. Daniel Mauricio Montenegro Reyes, dextronomo@gmail.com

Comentarios