W3docs

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.SimpleNamespace pour l'accès par point
  • Utiliser collections.namedtuple pour 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 attribute

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

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

Quand 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.com

Vous 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

OutilMutableAccès par pointMéthodesAnnotations de typeIdéal pour
dictOuiNon (["key"])NonNonChamps dynamiques / inconnus
SimpleNamespaceOuiOuiNonNonConfig ad hoc, fixtures de test
namedtupleNonOuiNonPartielEnregistrements immuables, petites données
classOuiOuiOuiVia annotationsPOO avec comportement
@dataclassOui*OuiOuiOuiEnregistrements structurés avec méthodes

*frozen=True rend un dataclass immuable.

Règle générale :

  • Utilisez un dict lorsque la structure n'est pas connue à l'avance.
  • Utilisez SimpleNamespace lorsque vous voulez un accès par point sans définir de classe.
  • Utilisez namedtuple pour des enregistrements simples et immuables (coordonnées, couleurs, lignes).
  • Utilisez une class ordinaire lorsque vous avez besoin de méthodes et de la POO complète.
  • Utilisez @dataclass lorsque 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

Pratique

Pratique
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?