Agrupación de Variables en Python
Aprende a agrupar variables relacionadas en Python usando clases, dataclasses, named tuples, SimpleNamespace y diccionarios — con ejemplos ejecutables.
Cuando un programa necesita rastrear varios datos relacionados — el nombre, la edad y el correo electrónico de un usuario, por ejemplo — almacenarlos en variables separadas e inconexas se vuelve difícil de gestionar. Python ofrece varias herramientas para agrupar variables bajo un único nombre, de modo que viajen juntas y permanezcan organizadas. Este capítulo explica los enfoques más comunes, cuándo usar cada uno y los compromisos que implican.
Temas tratados:
- Por qué es importante agrupar variables
- Usar un diccionario simple
- Usar
types.SimpleNamespacepara acceso por punto - Usar
collections.namedtuplepara registros inmutables ligeros - Usar una clase con
__init__ - Usar
@dataclass(Python 3.7+) para la sintaxis más limpia - Elegir la herramienta adecuada
¿Por qué Agrupar Variables?
Supongamos que estás escribiendo un script que procesa cuentas de usuario. Sin agruparlas, podrías escribir:
user_name = "Alice"
user_age = 30
user_email = "[email protected]"Esto funciona para un solo usuario, pero falla en el momento en que necesitas dos usuarios o pasar datos a una función:
def greet(name, age, email):
print(f"Hello {name}, age {age} ({email})")
greet(user_name, user_age, user_email)Tres argumentos separados deben mantenerse sincronizados en todas partes. La agrupación resuelve esto al agrupar los datos:
user = {"name": "Alice", "age": 30, "email": "[email protected]"}
def greet(user):
print(f"Hello {user['name']}, age {user['age']} ({user['email']})")
greet(user)Ahora la firma de la función tiene un parámetro en lugar de tres, y agregar un nuevo campo solo implica modificar el diccionario.
Usar un Diccionario
Un diccionario de Python es la forma más sencilla de agrupar variables con nombre. Las claves son strings; los valores pueden ser de cualquier tipo.
point = {"x": 10, "y": 20, "label": "origin"}
print(point["x"]) # 10
print(point["label"]) # origin
# Update a field
point["x"] = 15
print(point)
# {'x': 15, 'y': 20, 'label': 'origin'}Cuándo usarlo: Agrupación rápida y ocasional, datos JSON, situaciones donde el conjunto de campos no se conoce de antemano.
Desventajas: Se accede a los campos con claves de tipo string (point["x"]), lo que es más verboso que la notación por punto y no ofrece autocompletado en el IDE.
Usar types.SimpleNamespace
SimpleNamespace es un envoltorio ligero que proporciona acceso por punto en un espacio de nombres ad-hoc sin necesidad de escribir una clase.
from types import SimpleNamespace
point = SimpleNamespace(x=10, y=20, label="origin")
print(point.x) # 10
print(point.label) # origin
# Update a field
point.x = 15
print(point)
# namespace(x=15, y=20, label='origin')Los objetos SimpleNamespace son mutables — puedes añadir, cambiar o eliminar atributos en cualquier momento:
from types import SimpleNamespace
config = SimpleNamespace(debug=False, timeout=30)
config.debug = True # update
config.retries = 3 # add new attribute
del config.timeout # remove
print(vars(config))
# {'debug': True, 'retries': 3}Cuándo usarlo: Reemplazar un diccionario cuando quieres acceso por punto pero no necesitas métodos ni verificación de tipos. Ideal para fixtures de prueba y objetos de configuración simples.
Usar collections.namedtuple
Un namedtuple es un registro inmutable y ligero. Se comporta como una tupla normal, pero permite acceder a los campos por nombre además de por índice.
from collections import namedtuple
# Define the type once
Point = namedtuple("Point", ["x", "y"])
# Create an instance
p = Point(x=10, y=20)
print(p.x) # 10
print(p.y) # 20
print(p[0]) # 10 — index access still works
print(p) # Point(x=10, y=20)Dado que las instancias de namedtuple son inmutables, no puedes cambiar un campo después de su creación:
from collections import namedtuple
Color = namedtuple("Color", ["red", "green", "blue"])
white = Color(255, 255, 255)
# white.red = 0 # AttributeError: can't set attributeSi necesitas una copia modificada, usa el método _replace() — devuelve una nueva instancia:
from collections import namedtuple
Color = namedtuple("Color", ["red", "green", "blue"])
white = Color(255, 255, 255)
grey = white._replace(red=128, green=128, blue=128)
print(grey)
# Color(red=128, green=128, blue=128)Cuándo usarlo: Registros inmutables donde los nombres de los campos importan — coordenadas, colores RGB, filas de base de datos. Menor huella de memoria que una clase completa.
Usar una Clase
Para variables agrupadas que también necesitan comportamiento (métodos), define una clase con un método __init__:
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def greet(self):
return f"Hello, I am {self.name} and I am {self.age} years old."
alice = User("Alice", 30, "[email protected]")
print(alice.name) # Alice
print(alice.greet()) # Hello, I am Alice and I am 30 years old.
# Update a field
alice.age = 31
print(alice.age) # 31Varias instancias permanecen independientes — cada una tiene su propia copia de name, age y email:
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
alice = User("Alice", 30, "[email protected]")
bob = User("Bob", 25, "[email protected]")
print(alice.name, bob.name) # Alice BobCuándo usarlo: Siempre que los datos agrupados también necesiten métodos, lógica de validación o herencia. Las clases son la base de la programación orientada a objetos en Python — consulta Clases y Objetos en Python para una explicación completa.
Usar @dataclass (Python 3.7+)
El decorador @dataclass genera automáticamente __init__, __repr__ y __eq__ a partir de los campos de clase anotados, eliminando la mayor parte del código repetitivo:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
label: str = "unnamed"
p = Point(x=3.0, y=4.0)
print(p) # Point(x=3.0, y=4.0, label='unnamed')
print(p.label) # unnamed
p.label = "A"
print(p) # Point(x=3.0, y=4.0, label='A')Los campos con un valor predeterminado deben ir después de los campos sin uno (la misma regla que los argumentos de función normales).
Dataclass inmutable con frozen=True
Pasa frozen=True para evitar que cualquier campo se modifique después de la creación — comportamiento similar a un namedtuple pero con todas las capacidades de una clase:
from dataclasses import dataclass
@dataclass(frozen=True)
class RGB:
red: int
green: int
blue: int
white = RGB(255, 255, 255)
print(white)
# RGB(red=255, green=255, blue=255)
# white.red = 0 # FrozenInstanceError: cannot assign to field 'red'Agrupar múltiples registros en una lista
Los dataclasses funcionan de forma natural con listas cuando necesitas una colección de registros:
from dataclasses import dataclass
from typing import List
@dataclass
class Product:
name: str
price: float
in_stock: bool = True
inventory: List[Product] = [
Product("Widget", 9.99),
Product("Gadget", 24.99),
Product("Doohickey", 4.50, in_stock=False),
]
for item in inventory:
status = "available" if item.in_stock else "out of stock"
print(f"{item.name}: ${item.price:.2f} ({status})")Salida:
Widget: $9.99 (available)
Gadget: $24.99 (available)
Doohickey: $4.50 (out of stock)Para el conjunto completo de funcionalidades de los dataclasses, incluyendo field(), __post_init__ y herencia, consulta Python Dataclasses.
Agrupar Variables con Atributos de Clase
A veces quieres constantes compartidas asociadas a un grupo en lugar de datos por instancia. Los atributos de clase (definidos directamente en el cuerpo de la clase, fuera de __init__) se comparten entre todas las instancias:
class AppConfig:
MAX_RETRIES = 3
TIMEOUT = 30
BASE_URL = "https://api.example.com"
print(AppConfig.MAX_RETRIES) # 3
print(AppConfig.BASE_URL) # https://api.example.comNo necesitas instanciar AppConfig para leer sus atributos — trata la clase en sí como un espacio de nombres para constantes relacionadas. Este es un patrón ligero para grupos de configuración. Para una discusión más completa sobre atributos de clase versus atributos de instancia, consulta Clases y Objetos en Python.
Elegir la Herramienta Adecuada
| Herramienta | Mutable | Acceso por punto | Métodos | Type hints | Mejor para |
|---|---|---|---|---|---|
dict | Sí | No (["key"]) | No | No | Campos dinámicos / desconocidos |
SimpleNamespace | Sí | Sí | No | No | Configuración ad-hoc, fixtures de prueba |
namedtuple | No | Sí | No | Parcial | Registros inmutables, datos pequeños |
class | Sí | Sí | Sí | Mediante anotaciones | POO con comportamiento |
@dataclass | Sí* | Sí | Sí | Sí | Registros estructurados con métodos |
*frozen=True hace que un dataclass sea inmutable.
Regla general:
- Usa
dictcuando la estructura no se conoce de antemano. - Usa
SimpleNamespacecuando quieres acceso por punto sin definir una clase. - Usa
namedtuplepara registros simples e inmutables (coordenadas, colores, filas). - Usa una
classnormal cuando necesitas métodos y POO completa. - Usa
@dataclasscuando necesitas un registro estructurado con métodos opcionales — te ofrece lo máximo con el mínimo código repetitivo.
Temas Relacionados
- Variables en Python — cómo funcionan las variables y las reglas de nomenclatura
- Nombres de Variables — convenciones de nomenclatura y buenas prácticas
- Variables Globales — variables a nivel de módulo y la palabra clave
global - Clases y Objetos en Python — explicación completa de POO
- Python Dataclasses — inmersión profunda en
@dataclass - Asignar Múltiples Valores — desempaquetado y asignación múltiple