top of page
Foto del escritorBraulio Madrid

Estructuras en Python




En lenguajes como C o C++ que aprovechan el paradigma funcional, en los 90s aún no se implementaba el paradigma orientado a objetos de forma masiva, sobre todo en C. Si ya eres viejo de seguirme en este blog y te interesaron los tutoriales de shaders que hay un montón, habrás notado que CG es básicamente lenguaje C con algunos cambios y solo enfocado a gráficos, cada vez que queríamos hacer un tipo de dato complejo para pasarlo empaquetado dentro de una función, lo hacíamos a través de un #struct.


Ahora estamos hablando de Python, en este lenguaje no hay tampoco algo como un struct donde podamos empaquetar un montón de variables y pasarlos como un parámetro dentro de una función, sin embargo hay alternativas.


Uso de clases.


Como mencioné arriba, un struct no es mas que un conjunto de datos asociados a un identificador, en Python hay esencialmente dos formas, la primera es crear una clase.



class Cliente:
	def __init__(self,nombre,identidad,email,telefono):
		self.nombre = nombre
		self.identidad = identidad
		self.email = email
		self.telefono = telefono

# El acceso a los atributos es bastante trivial
cliente = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")
print(cliente.nombre)
print(cliente.identificacion)
print(cliente.email)
print(cliente.telefono)

Usar clases nos permite hacer chequeos de información, como verificar si un correo es válido.


class Cliente:
	def __init__(self,nombre,identidad,email,telefono):
		self.nombre = nombre
		self.identidad = identidad
		if "@" not in email:
			raise.ValueError("El e-mail no es valido")
		self.email = email
		self.telefono = telefono

También nos permite asignar valores predeterminados como en cualquier función.

class Cliente:
	def __init__(self,nombre,identidad,email = None,telefono = None):
		self.nombre = nombre
		self.identidad = identidad
		if "@" not in email:
			raise.ValueError("El e-mail no es valido")
		self.email = email
		self.telefono = telefono

Las clases parecen funcionar bien siempre y cuando tengamos una estructura bien definida, pero en el caso que no sepamos que datos vamos a necesitar o si pensamos ampliar los datos sobre la marcha, un simple diccionario puede servir bien para la tarea.


cliente = {
	"nombre" : "Juan Perez",
	"identidad" : "43327846",
	"email" : "juanperez@darkcom.dev",
	"telefono : 3113458762"
	}

print(cliente["nombre"])
print(cliente["identidad"])
print(cliente["email"])
print(cliente["telefono"])

Si usamos los diccionarios, podemos aprovecharnos de las comparaciones por defecto en el caso que queramos comparar dos clientes.


Si hacemos usos de clases, podemos hacerlo mediante el método mágico `__eq__()` que chequee la igualdad de cada uno de los atributos.


class Cliente:
	def __init__(self,nombre,identidad,email,telefono):
		self.nombre = nombre
		self.identidad = identidad
		self.email = email
		self.telefono = telefono
		
	def __eq__(self, other):
		
		return (self.nombre == other.nombre and 	self.identidad == other.identidad and self.email == other.email and self.telefono == other.telefono)
		
cliente1 = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")

cliente2 = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")

print(cliente1 == cliente2)	#True

Uso de diccionarios.


Sin esta definición, cada instancia de la clase, por mas que tengan los mismos nombres, los mismos teléfonos y todos los datos así sean iguales, la comparación va a devolver falso, simplemente porque los identificadores en memoria son distintos.


Con la función mágica #__eq__() podemos usar comparaciones sin ningún problema `== o !=`


En cambio con los diccionarios estas comparaciones funcionan sin implementar código adicional.


cliente1 = {
	"nombre" : "Juan Perez",
	"identidad" : "43327846",
	"email" : "juanperez@darkcom.dev",
	"telefono : 3113458762"
	}
cliente2 = {
	"nombre" : "Juan Perez",
	"identidad" : "43327846",
	"email" : "juanperez@darkcom.dev",
	"telefono : 3113458762"
	}
print(cliente1 == cliente2)	#True

Otra ventaja que tienen los diccionarios es que es muy fácil convertir los datos a archivos JSON o XML, esto es particularmente útil para compartir información entre aplicaciones en la red por ejemplo.



import json

cliente = {
	"nombre" : "Juan Perez",
	"identidad" : "43327846",
	"email" : "juanperez@darkcom.dev",
	"telefono : 3113458762"
	}
	
print(json.dumps(cliente))

El segundo método es usar una colección llamada #namedtuple, esto implica un poco de trabajo extra.



from collections import namedtuple

Cliente = namedtuple("Cliente",("nombre","identificacion","email","telefono"))
cliente = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")

print(cliente.nombre)
print(cliente.identificacion)
print(cliente.email)
print(cliente.telefono)

También soporta la comparación sin código extra.


from collections import namedtuple

Cliente = namedtuple("Cliente",("nombre","identificacion","email","telefono"))

cliente1 = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")
cliente2 = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")

print(cliente1 == cliente2)    #True

Uso de Attrs.


Ahora bien cuando necesitamos la validación de los datos y la rigidez de las clases, combinado con la rapidez, la versatilidad y las comparaciones de los diccionarios y las #tuplas nombradas, allí nos encontramos el maravilloso módulo de #attr.



import attr

@attr.s
class Cliente:
	nombre = attr.ib()
	identificacion = attr.ib()
	email = attr.ib()
	telefono = attr.ib()

cliente = Cliente("Juan Perez", "43327846", "juanperez@darkcom.dev", "3113458762")

print(cliente.nombre)
print(cliente.identificacion)
print(cliente.email)
print(cliente.telefono)

Los operadores de igualdad y desigualdad sirve para toda la clase con el #decorador attr.s

y chequean que todos los atributos de la clase tengan el mismo valor, como ocurre con los diccionarios y las tuplas nombradas.


Podemos incluso iniciar los valores sin usar la definición __init__()


import attr

@attr.s
class Cliente:
	nombre = attr.ib()
	identificacion = attr.ib()
	email = attr.ib(default = None)
	telefono = attr.ib(default = None)
	
	@email.validator
	def is_email(self, attribute, value):
		if "@" not in value:
			raise ValueError("Not a valid email address.")

Además con attr podremos convertir a otros tipos de colección como diccionarios y tuplas.


print(attr.asdict(cliente))
print(attr.astuple(cliente))

Este módulo no se incluye en las librerías estándar de Python aunque algo similar hace el módulo estándar de #dataclasses, a partir de la versión 3.7 de Python. Para instalarlo simplemente ejecuta.


pip instal attrs

En resumen cuando necesites una estructura de datos, mira la siguiente tabla para escoger lo que mas se adecue a tus necesidades.


+---------------+---------------+---------------+---------------+

| |rigidez y |versatilidad, |funciones |

| |validaciones |rapidez y |geniales |

| | |comparaciones | |

+---------------+---------------+---------------+---------------+

|Clases normales|SI |NO |NO |

+---------------+---------------+---------------+---------------+

|Diccionarios | | | |

|namedtuples |NO |SI |NO |

+---------------+---------------+---------------+---------------+

|Attrs |SI |SI |SI |

+---------------+---------------+---------------+---------------+


Conclusiones.


En un principio puede que Python parezca carente de ciertas características, pero conforme vas usando el lenguaje vas encontrando alternativas para hacer lo mismo, incluso a veces mejor.


Lo importante es saber que tipo de dato usar que sea versátil y que aporte funcionalidad, en mi experiencia he notado que según que ámbito uno usa mas algunos mas que otros, en videojuegos y todo aquello que tenga que ver con gameplay, hago mas uso de clases que de diccionarios y para la generación de datos, sobre todo el manejo de textos hago mas uso de diccionarios.


Con el tiempo y la experiencia, el instinto lo va guiando sobre que estructuras de datos usar. Bueno no siendo mas nos vemos en un próximo Blog.


6 visualizaciones0 comentarios

Entradas recientes

Ver todo

Comments


bottom of page