Python no tiene ningún mecanismo especial para crear estructuras, como la palabra reservada struct
en C/C++ y en otros lenguajes. ¿Cómo podemos suplir esta necesidad? Que no nos sorprenda: una estructura no es más que un conjunto de valores asociados a un identificador. Dado que el lenguaje provee varias formas de conseguir esto, no fue necesario añadir una nueva.
En la sección de microrecursos tenemos un pequeño artículo sobre cómo crear una estructura en Python. Allí recomendamos dos métodos: el primero consiste en simplemente crear una clase.
Supongamos que necesitamos una estructura para representar clientes con un nombre, una dirección de correo electrónico, y un número de teléfono. Ello se consigue, según este primer método, de la siguiente manera.
class Customer: def __init__(self, name, email, phone): self.name = name self.email = email self.phone = phone
Luego, la creación de un cliente y el acceso a sus atributos es bastante trivial:
customer = Customer("Pablo", "pablo@empresa.com", "123-456-789") print(customer.name) print(customer.email) print(customer.phone)
Utilizar clases nos permite hacer chequeos en la información que se provee como atributos. Por ejemplo, si queremos asegurarnos de que se trata de una dirección de correo electrónico válida:
class Customer: def __init__(self, name, email, phone): self.name = name if "@" not in email: raise ValueError("Not a valid email address.") self.email = email self.phone = phone
O bien asignar valores por defecto, como en cualquier otra función:
class Customer: def __init__(self, name, email=None, phone=None): self.name = name if email is not None and "@" not in email: raise ValueError("Not a valid email address.") self.email = email self.phone = phone
Este método parece ser ideal si necesitamos una estructura con un conjunto de atributos y validaciones bien definidos. Pero cuando manejamos información de estructura variada queremos algo más sencillo y versátil. En ese caso un simple diccionario puede suplir a una estructura.
customer = { "name": "Pablo", "email": "pablo@empresa.com", "phone": "123-456-789" } print(customer["name"]) print(customer["email"]) print(customer["phone"])
Además, de los diccionarios podemos aprovechar sus comparaciones por defecto. Si queremos comparar dos clientes, en nuestra estructura basada en clases tendríamos que definir el método mágico __eq__()
que chequee la igualdad de cada uno de los atributos:
class Customer: # ... def __eq__(self, other): return (self.name == other.email and self.email == other.email and self.phone == other.phone) customer1 = Customer("Pablo", "pablo@empresa.com", "123-456-789") customer2 = Customer("Pablo", "pablo@empresa.com", "123-456-789") print(customer1 == customer2) # True
(Sin esta definición, dos instancias de Customer
siempre son distintas, por más que tengan los mismos atributos y valores).
En cambio, en los diccionarios, los operadores de igualdad (==
) y desigualdad (!=
) se comportan de esta manera sin ningún código adicional:
customer1 = { "name": "Pablo", "email": "pablo@empresa.com", "phone": "123-456-789" } customer2 = { "name": "Pablo", "email": "pablo@empresa.com", "phone": "123-456-789" } print(customer1 == customer2) # True
También, convertir un diccionario a formato JSON o XML es bastante sencillo, particularmente útil para intercambiar información con otras aplicaciones en la red. Por ejemplo:
import json customer = { "name": "Pablo", "email": "pablo@empresa.com", "phone": "123-456-789" } print(json.dumps(customer))
Esto implicaría un poco de trabajo extra al trabajar con clases convencionales.
El segundo método que proponíamos era usando la colección estándar namedtuple
:
from collections import namedtuple Customer = namedtuple("Customer", ("name", "email", "phone")) customer = Customer(name="Pablo", email="pablo@empresa.com", phone="123-456-789") print(customer.name) print(customer.email) print(customer.phone)
Que a su vez soporta la comparación de sus atributos sin código extra.
customer1 = Customer(name="Pablo", email="pablo@empresa.com", phone="123-456-789") customer2 = Customer(name="Pablo", email="pablo@empresa.com", phone="123-456-789") print(customer1 == customer2) # True
Ahora bien, cuando necesitamos combinar la rigidez y la capacidad de validar datos que nos brindan las clases con la versatilidad, rapized y comparaciones por defecto de los diccionarios o tuplas con nombre, allí es cuando nos encontramos con el maravilloso módulo attrs:
import attr @attr.s class Customer: name = attr.ib() email = attr.ib() phone = attr.ib() customer = Customer(name="Pablo", email="pablo@empresa.com", phone="123-456-789") print(customer.name) print(customer.email) print(customer.phone)
Los operadores de igualdad y desigualdad valen para toda clase definida con el decorador attr.s
y chequean que todos los atributos definidos tengan el mismo valor (al igual que los diccionarios y las tuplas con nombre).
customer1 = Customer(name="Pablo", email="pablo@empresa.com", phone="123-456-789") customer2 = Customer(name="Pablo", email="pablo@empresa.com", phone="123-456-789") print(customer1 == customer2) # True
Podemos, incluso, indicar valores por defecto y validadores sin siquiera crear el método __init__()
:
@attr.s class Customer: name = attr.ib() email = attr.ib(default=None) phone = 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, usando attrs convertimos fácilmente nuestra estructura a otras colecciones:
print(attr.asdict(customer)) print(attr.astuple(customer))
Este increíble módulo no se incluye en la librería estándar (aunque algo similar intenta conseguirse con el módulo estándar dataclasses
, a partir de Python 3.7). Para instalarlo, simplemente ejecuta
pip install attrs
En resumen, cuando tu código te solicite una estructura, recuerda la siguiente tabla para saber por qué solución inclinarte.
Rigidez y validaciones | Versatilidad, rapidez y comparaciones | Otras funcionalidades geniales | |
Clases convencionales | Sí | No | No |
Diccionarios / namedtuple |
No | Sí | No |
attrs | Sí | Sí | Sí |
Curso online 👨💻
¡Ya lanzamos el curso oficial de Recursos Python en Udemy!
Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter
en 2024.
Consultoría 💡
Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.