PythonTipado Estático y DataclassesLección 12 de 24
Dataclasses y Protocols
Las dataclasses (Python 3.7+) eliminan el código repetitivo de las clases que solo guardan datos. Con @dataclass, Python genera automáticamente __init__, __repr__ y __eq__. Son perfectas para modelos de datos, DTOs y cualquier clase cuya función principal sea almacenar información estructurada.
Los Protocols (Python 3.8+) definen interfaces de forma estructural (duck typing estático). A diferencia de la herencia, una clase satisface un Protocol simplemente por tener los métodos correctos, sin declararlo explícitamente. Esto combina la flexibilidad del duck typing con la seguridad de las type hints.
from dataclasses import dataclass, field, asdict, astuple
from typing import Protocol
# Dataclass - elimina __init__, __repr__, __eq__ boilerplate
@dataclass
class Punto:
x: float
y: float
def distancia_origen(self) -> float:
return (self.x**2 + self.y**2) ** 0.5
p = Punto(3.0, 4.0)
print(p) # Punto(x=3.0, y=4.0)
print(p.distancia_origen()) # 5.0
print(p == Punto(3.0, 4.0)) # True (compara por valor)
# Campos con valores por defecto
@dataclass
class Producto:
nombre: str
precio: float
stock: int = 0
etiquetas: list = field(default_factory=list) # ¡No usar [] directamente!
def aplicar_descuento(self, porcentaje: float) -> None:
self.precio *= (1 - porcentaje / 100)
# frozen=True - inmutable (como namedtuple)
@dataclass(frozen=True)
class Coordenada:
lat: float
lon: float
# asdict - convierte a diccionario
prod = Producto("Python Book", 29.99, 10)
print(asdict(prod))
# {"nombre": "Python Book", "precio": 29.99, "stock": 10, "etiquetas": []}
# Protocol - duck typing estático
class Serializable(Protocol):
def to_json(self) -> str: ...
class Usuario:
def __init__(self, nombre: str):
self.nombre = nombre
def to_json(self) -> str:
import json
return json.dumps({"nombre": self.nombre})
# Usuario implementa Serializable sin herencia explícita
def guardar(obj: Serializable) -> None:
print(obj.to_json())
guardar(Usuario("Ana")) # Válido
Consejo⚠️ Nunca uses field default mutable directamente: @dataclass class Foo: items: list = []. Usa field(default_factory=list) para evitar que todas las instancias compartan el mismo objeto.
Practica esta lección
Resuelve los ejercicios del curso de Python en la app, gratis y con corrección automática.
Empezar a practicar