Regrouper des variables en Python
Apprenez à regrouper des variables en Python avec des classes, dataclasses, namedtuples, SimpleNamespace et dicts — avec des exemples exécutables.
Lorsqu'un programme doit suivre plusieurs données connexes — le nom, l'âge et l'e-mail d'un utilisateur, par exemple — les stocker dans des variables séparées et non liées devient difficile à gérer. Python offre plusieurs outils pour regrouper des variables sous un seul nom afin qu'elles voyagent ensemble et restent organisées. Ce chapitre explique les approches les plus courantes, quand utiliser chacune d'elles et les compromis à prendre en compte.
Sujets abordés :
- Pourquoi le regroupement de variables est important
- Utiliser un dictionnaire simple
- Utiliser
types.SimpleNamespacepour l'accès par point - Utiliser
collections.namedtuplepour des enregistrements immuables légers - Utiliser une classe avec
__init__ - Utiliser
@dataclass(Python 3.7+) pour la syntaxe la plus concise - Choisir le bon outil
Pourquoi regrouper des variables ?
Supposons que vous écriviez un script qui traite des comptes utilisateurs. Sans regroupement, vous pourriez écrire :
user_name = "Alice"
user_age = 30
user_email = "[email protected]"Cela fonctionne pour un seul utilisateur, mais pose problème dès que vous avez besoin de deux utilisateurs ou que vous passez des données à une fonction :
def greet(name, age, email):
print(f"Hello {name}, age {age} ({email})")
greet(user_name, user_age, user_email)Trois arguments distincts doivent rester synchronisés partout. Le regroupement résout ce problème en regroupant les données :
user = {"name": "Alice", "age": 30, "email": "[email protected]"}
def greet(user):
print(f"Hello {user['name']}, age {user['age']} ({user['email']})")
greet(user)Désormais, la signature de la fonction n'a qu'un seul paramètre au lieu de trois, et l'ajout d'un nouveau champ ne touche que le dictionnaire.
Utiliser un dictionnaire
Un dictionnaire Python est la manière la plus simple de regrouper des variables nommées. Les clés sont des strings ; les valeurs peuvent être de n'importe quel type.
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'}Quand l'utiliser : Regroupement ponctuel rapide, données JSON, situations où l'ensemble des champs n'est pas connu à l'avance.
Inconvénients : Vous accédez aux champs avec des clés string (point["x"]), ce qui est plus verbeux que la notation par point et ne bénéficie pas de l'autocomplétion dans l'IDE.
Utiliser types.SimpleNamespace
SimpleNamespace est un wrapper léger qui vous donne un accès par point sur un espace de noms ad hoc sans écrire de 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')Les objets SimpleNamespace sont mutables — vous pouvez ajouter, modifier ou supprimer des attributs à tout moment :
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}Quand l'utiliser : Remplacer un dictionnaire lorsque vous voulez un accès par point mais n'avez pas besoin de méthodes ni de vérification de type. Idéal pour les fixtures de test et les objets de configuration simples.
Utiliser collections.namedtuple
Un namedtuple est un enregistrement immuable et léger. Il se comporte comme un tuple ordinaire mais permet d'accéder aux champs par nom ainsi que par index.
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)Comme les instances de namedtuple sont immuables, vous ne pouvez pas modifier un champ après la création :
from collections import namedtuple
Color = namedtuple("Color", ["red", "green", "blue"])
white = Color(255, 255, 255)
# white.red = 0 # AttributeError: can't set attributeSi vous avez besoin d'une copie modifiée, utilisez la méthode _replace() — elle retourne une nouvelle instance :
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)Quand l'utiliser : Enregistrements immuables où les noms de champs importent — coordonnées, couleurs RGB, lignes de base de données. Empreinte mémoire plus légère qu'une classe complète.
Utiliser une classe
Pour des variables groupées qui ont également besoin de comportement (méthodes), définissez une classe avec une méthode __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) # 31Plusieurs instances restent indépendantes — chacune possède sa propre copie de name, age et 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 BobQuand l'utiliser : Chaque fois que les données groupées nécessitent des méthodes, une logique de validation ou de l'héritage. Les classes sont le fondement de la programmation orientée objet en Python — voir Classes et objets Python pour une explication complète.
Utiliser @dataclass (Python 3.7+)
Le décorateur @dataclass génère automatiquement __init__, __repr__ et __eq__ à partir des champs de classe annotés, éliminant la majeure partie du code répétitif :
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')Les champs avec une valeur par défaut doivent venir après les champs sans valeur par défaut (même règle que pour les arguments de fonctions ordinaires).
Dataclass immuable avec frozen=True
Passez frozen=True pour empêcher toute modification d'un champ après la création — comportement similaire à un namedtuple mais avec toutes les capacités d'une 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'Regrouper plusieurs enregistrements dans une liste
Les dataclasses fonctionnent naturellement avec les listes lorsque vous avez besoin d'une collection d'enregistrements :
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})")Résultat :
Widget: $9.99 (available)
Gadget: $24.99 (available)
Doohickey: $4.50 (out of stock)Pour l'ensemble complet des fonctionnalités des dataclasses, notamment field(), __post_init__ et l'héritage, voir Python Dataclasses.
Regrouper des variables avec des attributs de classe
Parfois, vous souhaitez des constantes partagées attachées à un groupe plutôt que des données par instance. Les attributs de classe (définis directement dans le corps de la classe, en dehors de __init__) sont partagés entre toutes les instances :
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.comVous n'avez pas besoin d'instancier AppConfig pour lire ses attributs — traitez la classe elle-même comme un espace de noms pour les constantes associées. C'est un modèle léger pour les groupes de configuration. Pour une discussion plus approfondie sur les attributs de classe versus les attributs d'instance, voir Classes et objets Python.
Choisir le bon outil
| Outil | Mutable | Accès par point | Méthodes | Annotations de type | Idéal pour |
|---|---|---|---|---|---|
dict | Oui | Non (["key"]) | Non | Non | Champs dynamiques / inconnus |
SimpleNamespace | Oui | Oui | Non | Non | Config ad hoc, fixtures de test |
namedtuple | Non | Oui | Non | Partiel | Enregistrements immuables, petites données |
class | Oui | Oui | Oui | Via annotations | POO avec comportement |
@dataclass | Oui* | Oui | Oui | Oui | Enregistrements structurés avec méthodes |
*frozen=True rend un dataclass immuable.
Règle générale :
- Utilisez un
dictlorsque la structure n'est pas connue à l'avance. - Utilisez
SimpleNamespacelorsque vous voulez un accès par point sans définir de classe. - Utilisez
namedtuplepour des enregistrements simples et immuables (coordonnées, couleurs, lignes). - Utilisez une
classordinaire lorsque vous avez besoin de méthodes et de la POO complète. - Utilisez
@dataclasslorsque vous avez besoin d'un enregistrement structuré avec des méthodes optionnelles — il vous offre le plus de fonctionnalités avec le moins de code répétitif.
Sujets connexes
- Variables Python — comment fonctionnent les variables et les règles de nommage
- Noms de variables — conventions de nommage et bonnes pratiques
- Variables globales — variables au niveau du module et le mot-clé
global - Classes et objets Python — explication complète de la POO
- Python Dataclasses — exploration approfondie de
@dataclass - Assigner plusieurs valeurs — décompression et affectations multiples