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)
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¶
Alvaro Mauricio Montenegro Díaz, ammontenegrod@unal.edu.co
Daniel Mauricio Montenegro Reyes, dextronomo@gmail.com