W3docs

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.SimpleNamespace para acceso por punto
  • Usar collections.namedtuple para 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 attribute

Si 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)      # 31

Varias 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 Bob

Cuá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.com

No 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

HerramientaMutableAcceso por puntoMétodosType hintsMejor para
dictNo (["key"])NoNoCampos dinámicos / desconocidos
SimpleNamespaceNoNoConfiguración ad-hoc, fixtures de prueba
namedtupleNoNoParcialRegistros inmutables, datos pequeños
classMediante anotacionesPOO con comportamiento
@dataclassSí*Registros estructurados con métodos

*frozen=True hace que un dataclass sea inmutable.

Regla general:

  • Usa dict cuando la estructura no se conoce de antemano.
  • Usa SimpleNamespace cuando quieres acceso por punto sin definir una clase.
  • Usa namedtuple para registros simples e inmutables (coordenadas, colores, filas).
  • Usa una class normal cuando necesitas métodos y POO completa.
  • Usa @dataclass cuando necesitas un registro estructurado con métodos opcionales — te ofrece lo máximo con el mínimo código repetitivo.

Temas Relacionados

Práctica

Práctica
In Python, what are the main reasons for grouping variables into classes?
In Python, what are the main reasons for grouping variables into classes?
Was this page helpful?