Entre diccionarios y data clases
En python tenemos algunos tipos de datos Ver Figura 1 conocidos como data structures estos tipos de datos son datos iterables que permiten guardar información y heredar algunas operaciones que nos hacen la vida un poco más fácil, entre estos se destaca el tipo de dato dict que junto a data clases será el tema que trataré en este artículo
Diccionarios
Este tipo de dato es uno de los más comunes sobre todo cuando se trabaja con aplicaciones web debido al estándar json ya que para python un objeto json es un diccionario, este funciona utilizando el concepto de mapping con clave:valor un ejemplo podría verse a continuación
import json
def load_json(location: str):
cars = []
with open(location, mode="r", encoding="utf-8") as file:
cars = json.loads(file.read())
return cars
print(load_json("cars.json"))
se ejecuta y se obtiene como resultado una lista de diccionarios
[{'price': 20000, 'owner': 'Patrick', 'discount': 0.0, 'brand': 'Chevrolet'}, {'price': 10000, 'owner': 'Andrew', 'discount': 0.0, 'brand': 'Chevrolet'}]
en el código anterior se puede apreciar cómo al hacer json.loads() esto convierte los objetos json a diccionarios y de esta manera se pueden tratar como datos en python
Tip #1
Probablemente al trabajar con dicts nos encontremos con una lista de dicts y en ella la necesidad de contar cuántos elementos existen dentro de la lista para ciertos keys, por ejemplo en el código anterior quisiera contar cuantos carros de la marca "Chevrolet" existen , para ello realizaremos el siguiente código
import json
import collections
def load_json(location: str):
cars = []
with open(location, mode="r", encoding="utf-8") as file:
cars = json.loads(file.read())
return cars
def count_car_by_key(cars: list[dict], key: str):
return collections.Counter([car[key] for car in cars])
cars = load_json("cars.json")
print(count_car_by_key(cars, "brand"))
se ejecuta el código se obtiene el siguiente resultado
Counter({'Chevrolet': 2})
En el anterior código se utiliza el módulo Collections este módulo es especializado para dar soporte a diferentes colecciones entre ellos el diccionario brindando algunas funciones y métodos que seguramente trataré en otro artículo para este utilizaré la función counter que permite agrupar los por el valor almacenado en una key dando como resultado las marcas de sus carros y las cantidades que existen por marca
Tip #2
Los diccionarios en python se encuentran desordenados y si se van agregando mas atributos van quedando sin un orden específico, pero en algunas situaciones quisiéramos imprimir la clave:valor ordenados por la clave, para ello siguiendo el ejemplo de los cars el código sería el siguiente
import json
import collections
def load_json(location: str):
cars = []
with open(location, mode="r", encoding="utf-8") as file:
cars = json.loads(file.read())
return cars
def count_car_by_key(cars: list[dict], key: str):
return collections.Counter([car[key] for car in cars])
def sort_dict(cars):
cars_sorted = []
for car in cars:
car_sorted = {}
for key, value in sorted(car.items(), key=lambda item: item[0]):
car_sorted[key] = value
cars_sorted.append(car_sorted)
return cars_sorted
cars = load_json("cars.json")
print(sort_dict(cars))
ejecuto el resultado sería el siguiente
[{'price': 20000, 'owner': 'Patrick', 'discount': 0.0, 'brand': 'Chevrolet'}, {'price': 10000, 'owner': 'Andrew', 'discount': 0.0, 'brand': 'Chevrolet'}]
[{'brand': 'Chevrolet', 'discount': 0.0, 'owner': 'Patrick', 'price': 20000}, {'brand': 'Chevrolet', 'discount': 0.0, 'owner': 'Andrew', 'price': 10000}]
en el código anterior se utiliza una función lambda para devolver los keys ordenados alfabéticamente, y utilizando la función items() que devuelve una tupla se guarda el key y su valor, de esta manera se ordena el diccionario sin perder sus valores. Algunos contras de usar dicts para trabajar es que este tipo de dato permite agregar datos de manera arbitraria es decir se puede llamar un key si no existe te lo crea, y esto puede suceder por error (escribir mal un key) aquí es en donde entran a jugar las clases y los objetos
Data clases
Es un decorador que a partir de la estructura de la clase que yo cree genera unos métodos especiales a diferencia de la creación de clases normal, este escribe un código por ti y te permite evitar escribir líneas de código innecesario, además este decorator facilita utilizar sugar syntax veamos un ejemplo en código
from dataclasses import dataclass, field
from pprint import pprint
@dataclass(order=True)
class Car:
sort_index: int = field(init=False, repr=False)
price: int
owner: str
discount: float = 0.1
def __post_init__(self):
self.sort_index = self.price
cars: list[Car] = []
cars.append(Car(price=20000, owner="Patrick", discount=0.0))
cars.append(Car(price=10000, owner="Andrew", discount=0.0))
pprint(sorted(cars))
Se ejecuta el código y se obtiene como resultado
[Car(price=10000, owner='Andrew', discount=0.0),
Car(price=20000, owner='Patrick', discount=0.0)]
el código anterior utiliza el decorador @dataclass este recibe unos argumentos order=True que le dirá a dataclass que genere el metodo de comparacion para ello debe agregarse un atributo llamado sort_index el cual utilizando field se le indica que no lo pida cuando inicializa una clase y también le indica repr=False para que cuando se imprima un objeto no lo muestre como atributo, finalmente agrego el método post_init que es llamado después de crear el objeto y se utiliza para decirle que valor tendrá sort_index en este caso el precio, por lo tal ordena una lista de objeto Car utilizando el precio
Tip #1
Como mencione para python los objetos en JSON son diccionarios esto es útil hasta cierto punto pero a veces se requiere convertir este tipo de dato a objetos por diferentes razones para ello lo hará utilizando el siguiente código
from dataclasses import dataclass, field
from pprint import pprint
@dataclass(order=True)
class Car:
sort_index: int = field(init=False, repr=False)
price: int
owner: str
discount: float = 0.1
def __post_init__(self):
self.sort_index = self.price
cars = [
{"price": 20000, "owner": "Patrick", "discount": 0.0},
{"price": 10000, "owner": "Andrew", "discount": 0.0},
]
cars = [Car(**car) for car in cars]
pprint(sorted(cars))
se ejecuta el código y se obtiene como resultado
[Car(price=10000, owner='Andrew', discount=0.0),
Car(price=20000, owner='Patrick', discount=0.0)]
en el código anterior se utiliza un código bastante pythonico aunque hay algunas situaciones que no me terminan de convencer sobre el idiomatic code ya que algunas veces el código no se entiende muy bien en este caso usamos list comprehension para crear una nueva lista recorriendo la lista de diccionarios y desempaquetando sus keys y pasandolos como valor a la clase, este permite crear un objeto de clase Car, al finalizar se imprime una lista de cars ordenados por precio. Anteriormente utilizamos data class que además también FastAPI hace poco permite en sus feature trabajar con dataclass aunque internamente siga usando pydantic el programador tendría la opción de usar este decorator
Tip # 2
pydantic ya lo he mencionado en varios de los artículos que he escrito, pero esta herramienta puede ser un complemento perfecto para trabajar con clases de forma pythonica veamos el mismo ejemplo de cars pero utilizando pydantic
from pydantic import BaseModel
class Car(BaseModel):
price: int
owner: str
discount: float = 0.1
def __lt__(self, other):
if other.__class__ is self.__class__:
return self.price < other.price
cars = [
{"price": 20000, "owner": "Patrick", "discount": 0.0},
{"price": 10000, "owner": "Andrew", "discount": 0.0},
]
cars = [Car(**car) for car in cars]
print(sorted(cars))
se ejecuta y se obtiene como resultado
[Car(price=10000, owner='Andrew', discount=0.0), Car(price=20000, owner='Patrick', discount=0.0)]
a pesar de que pydantic tiene una gran cantidad de validaciones relacionadas a los tipos de datos hay algunos metodos que aun tendremos que agregar en este caso dataclass uno de los métodos que generaba era el método __lt__ el cual estos se llaman métodos mágicos y sorted() los utiliza para saber sobre qué atributo ordenar por tal motivo se sobreescribe este método para poder comparar dos objetos
Conclusión
Los diccionarios son un tipo de dato que actualmente en desarrollo web se utiliza mucho debido a que para python los objetos json se transforman a dict por eso es importante conocer sus métodos y funciones para trabajar de manera eficiente con ellos, sin embargo en varias situaciones este tipo de dato puede quedarse un poco corto en lo que se necesita, para ello se opta para trabajar con objetos y clases las cuales son potenciadas con dataclass y pydantic que permiten hacer transformaciones y validaciones utilizando código pythonico