Agrupamento de Variáveis em Python
Aprenda a agrupar variáveis relacionadas em Python usando classes, dataclasses, named tuples, SimpleNamespace e dicts — com exemplos executáveis.
Quando um programa precisa rastrear vários dados relacionados — o nome, a idade e o e-mail de um usuário, por exemplo — armazená-los em variáveis separadas e desconectadas torna-se difícil de gerenciar. Python oferece diversas ferramentas para agrupar variáveis sob um único nome, de modo que elas se movam juntas e permaneçam organizadas. Este capítulo explica as abordagens mais comuns, quando usar cada uma e as trocas envolvidas.
Tópicos abordados:
- Por que agrupar variáveis é importante
- Usando um dicionário simples
- Usando
types.SimpleNamespacepara acesso por ponto - Usando
collections.namedtuplepara registros imutáveis leves - Usando uma classe com
__init__ - Usando
@dataclass(Python 3.7+) para a sintaxe mais limpa - Escolhendo a ferramenta certa
Por Que Agrupar Variáveis?
Suponha que você esteja escrevendo um script que processa contas de usuários. Sem agrupamento, você poderia escrever:
user_name = "Alice"
user_age = 30
user_email = "[email protected]"Isso funciona para um usuário, mas entra em colapso no momento em que você precisa de dois usuários ou passa dados para uma função:
def greet(name, age, email):
print(f"Hello {name}, age {age} ({email})")
greet(user_name, user_age, user_email)Três argumentos separados precisam permanecer sincronizados em todos os lugares. O agrupamento resolve isso ao reunir os dados:
user = {"name": "Alice", "age": 30, "email": "[email protected]"}
def greet(user):
print(f"Hello {user['name']}, age {user['age']} ({user['email']})")
greet(user)Agora a assinatura da função tem um parâmetro em vez de três, e adicionar um novo campo afeta apenas o dicionário.
Usando um Dicionário
Um dicionário Python é a maneira mais simples de agrupar variáveis nomeadas. As chaves são strings; os valores podem ser de qualquer 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'}Quando usar: Agrupamento rápido e pontual, dados JSON, situações em que o conjunto de campos não é conhecido de antemão.
Desvantagens: Você acessa os campos com chaves string (point["x"]), o que é mais verboso do que a notação por ponto e não oferece autocomplete na IDE.
Usando types.SimpleNamespace
SimpleNamespace é um invólucro leve que oferece acesso por ponto em um namespace ad-hoc sem precisar escrever uma classe.
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')Objetos SimpleNamespace são mutáveis — você pode adicionar, alterar ou excluir atributos a qualquer 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}Quando usar: Substituir um dicionário quando você quer acesso por ponto, mas não precisa de métodos ou verificação de tipos. Bom para fixtures de teste e objetos de configuração simples.
Usando collections.namedtuple
Um namedtuple é um registro imutável e leve. Ele se comporta como uma tupla comum, mas permite acessar os campos por nome, além 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)Como instâncias de namedtuple são imutáveis, você não pode alterar um campo após a criação:
from collections import namedtuple
Color = namedtuple("Color", ["red", "green", "blue"])
white = Color(255, 255, 255)
# white.red = 0 # AttributeError: can't set attributeSe precisar de uma cópia modificada, use o método _replace() — ele retorna uma nova instância:
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)Quando usar: Registros imutáveis em que os nomes dos campos importam — coordenadas, cores RGB, linhas de banco de dados. Ocupa menos memória do que uma classe completa.
Usando uma Classe
Para variáveis agrupadas que também precisam de comportamento (métodos), defina uma classe com um 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) # 31Múltiplas instâncias permanecem independentes — cada uma guarda sua própria cópia de name, age e 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 BobQuando usar: Sempre que os dados agrupados também precisam de métodos, lógica de validação ou herança. Classes são a base da programação orientada a objetos em Python — consulte Python Classes e Objetos para uma explicação completa.
Usando @dataclass (Python 3.7+)
O decorador @dataclass gera automaticamente __init__, __repr__ e __eq__ a partir dos campos de classe anotados, eliminando a maior parte do 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')Campos com valor padrão devem vir após os campos sem valor padrão (mesma regra dos argumentos de funções normais).
Dataclass imutável com frozen=True
Passe frozen=True para impedir que qualquer campo seja alterado após a criação — comportamento similar ao de um namedtuple, mas com todas as capacidades de uma classe:
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'Agrupando múltiplos registros em uma lista
Dataclasses funcionam naturalmente com listas quando você precisa de uma coleção 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})")Saída:
Widget: $9.99 (available)
Gadget: $24.99 (available)
Doohickey: $4.50 (out of stock)Para o conjunto completo de recursos de dataclasses, incluindo field(), __post_init__ e herança, consulte Python Dataclasses.
Agrupando Variáveis com Atributos de Classe
Às vezes você quer constantes compartilhadas anexadas a um grupo, em vez de dados por instância. Atributos de classe (definidos diretamente no corpo da classe, fora de __init__) são compartilhados por todas as instâncias:
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.comVocê não precisa instanciar AppConfig para ler seus atributos — trate a própria classe como um namespace para constantes relacionadas. Este é um padrão leve para grupos de configuração. Para uma discussão mais completa sobre atributos de classe versus atributos de instância, consulte Python Classes e Objetos.
Escolhendo a Ferramenta Certa
| Ferramenta | Mutável | Acesso por ponto | Métodos | Dicas de tipo | Melhor para |
|---|---|---|---|---|---|
dict | Sim | Não (["key"]) | Não | Não | Campos dinâmicos / desconhecidos |
SimpleNamespace | Sim | Sim | Não | Não | Config ad-hoc, fixtures de teste |
namedtuple | Não | Sim | Não | Parcial | Registros imutáveis, dados pequenos |
class | Sim | Sim | Sim | Via anotações | POO com comportamento |
@dataclass | Sim* | Sim | Sim | Sim | Registros estruturados com métodos |
*frozen=True torna um dataclass imutável.
Regra geral:
- Use
dictquando a estrutura não é conhecida de antemão. - Use
SimpleNamespacequando quiser acesso por ponto sem definir uma classe. - Use
namedtuplepara registros simples e imutáveis (coordenadas, cores, linhas). - Use uma
classcomum quando precisar de métodos e POO completa. - Use
@dataclassquando precisar de um registro estruturado com métodos opcionais — oferece o máximo com o mínimo de código repetitivo.
Tópicos Relacionados
- Python Variables — como as variáveis funcionam e regras de nomenclatura
- Variable Names — convenções de nomenclatura e boas práticas
- Global Variables — variáveis de nível de módulo e a palavra-chave
global - Python Classes e Objetos — explicação completa de POO
- Python Dataclasses — aprofundamento em
@dataclass - Assign Multiple Values — desempacotamento e atribuição múltipla