Quand une fonction doit partager un état avec le reste du module, le vrai sujet n’est pas une « fonction globale », mais la portée des noms et la façon dont Python traite l’affectation. Je vais montrer ce que fait réellement global, dans quels cas il est utile, pourquoi il déclenche souvent des erreurs, et quelles alternatives je privilégie pour garder un code plus lisible. Vous repartirez avec des exemples simples, un tableau de comparaison et une règle pratique pour savoir quand l’utiliser, ou l’éviter.
Les points à retenir avant d’utiliser un état global en Python
-
globalne crée pas une fonction : c’est une instruction qui dit à Python d’utiliser un nom défini au niveau du module. - Si une fonction ne fait que lire une variable globale, elle n’a généralement pas besoin de
global. - Le piège classique vient d’une affectation dans la fonction : Python considère alors le nom comme local par défaut.
-
nonlocalsert pour une variable d’une fonction englobante, pas pour une variable du module. - Pour un code maintenable, les paramètres, les valeurs de retour et les objets sont souvent meilleurs que l’état partagé.

Ce que fait vraiment l’instruction global
En Python, global est une directive de portée, pas un mécanisme magique. La documentation officielle rappelle qu’elle s’applique au bloc courant et qu’elle indique à l’interpréteur qu’un nom doit être résolu au niveau du module. Autrement dit, elle ne « rend » pas une fonction globale et ne modifie pas le comportement de Python partout dans le programme.
Le point le plus important est simple : si vous assignez à un nom dans une fonction, Python le traite comme local, sauf si vous le déclarez explicitement avec global. C’est pour cela que lire une variable globale est souvent possible sans rien écrire de spécial, alors que la réaffecter provoque vite une erreur ou un comportement inattendu.
Je résume la règle pratique ainsi : lecture seule, pas besoin de global ; réaffectation, déclaration obligatoire. La nuance paraît petite, mais elle explique à elle seule la majorité des bugs liés à la portée des variables. Et c’est justement là qu’il faut regarder les cas concrets pour comprendre ce qui se passe vraiment.
Lire, modifier ou réaffecter une variable globale
Le comportement change selon la nature de l’opération. Lire une valeur, modifier un objet mutable et réaffecter un nom ne sont pas la même chose pour Python, même si, à première vue, tout ressemble à une simple « mise à jour ».
compteur = 0
def afficher_compteur():
print(compteur) # lecture simple, pas besoin de global
def incrementer():
global compteur
compteur += 1
return compteurDans le premier cas, la fonction lit la variable du module. Dans le second, elle réaffecte le nom compteur, donc elle doit signaler explicitement que ce nom appartient à l’espace global du module. Sans global, Python comprendrait que compteur est local à la fonction, ce qui crée un conflit au moment où il faut lire sa valeur avant de l’incrémenter.
total = 10
def ajouter():
total += 1 # UnboundLocalError
return totalIci, l’erreur est classique : Python voit une affectation sur total, le considère comme local, puis se retrouve à devoir le lire avant qu’il ait reçu une valeur. C’est le fameux UnboundLocalError. La correction peut être global, mais ce n’est pas forcément la meilleure solution si la logique métier commence à grossir.
Il existe aussi un cas qui piège souvent les débutants : modifier un objet mutable n’est pas la même chose que réaffecter son nom.
journal = []
def ajouter_evenement():
journal.append("connexion réussie") # mutation, pas de global nécessaireLe nom journal n’est pas réassigné, seul l’objet liste est modifié. En revanche, si vous écrivez journal = journal + ["connexion réussie"], vous faites une réaffectation et la logique change complètement. Cette distinction vaut de l’or quand on relit un script plusieurs semaines plus tard, parce qu’elle évite de confondre mutation d’objet et rebinding du nom. Et c’est précisément ce qui amène à comparer global à nonlocal et aux paramètres classiques.
Comparer global, nonlocal et les paramètres
Quand je vois du code avec des variables partagées, je commence presque toujours par me demander si le bon outil n’est pas simplement une valeur de retour, un paramètre explicite ou une fermeture avec nonlocal. Le choix dépend surtout de l’endroit où vit l’état et de la durée pendant laquelle il doit exister.
| Outil | Portée visée | Usage typique | Limite principale |
|---|---|---|---|
global |
Variable du module | Compteur simple, état partagé très ponctuel, script court | Rend les dépendances implicites et complique les tests |
nonlocal |
Variable d’une fonction englobante | Fermeture qui doit conserver un état interne | Ne fonctionne pas au niveau du module |
| Paramètres + retour | Flux explicite entre fonctions | Logique métier, traitement de données, code réutilisable | Demande un peu plus de discipline à l’appelant |
| Objet ou dataclass | État regroupé dans une structure | Configuration, session, compteur, cache métier | Nécessite une petite architecture supplémentaire |
nonlocal est souvent la meilleure réponse quand une fonction interne doit ajuster un état défini dans la fonction parente. Exemple typique : un compteur encapsulé dans une fermeture. À l’inverse, si plusieurs fonctions du module manipulent la même donnée, je préfère presque toujours un objet dédié ou un conteneur explicite plutôt qu’un flot de variables globales. Le code devient plus lisible, et les effets de bord sont plus faciles à tracer.
Ce tableau aide à voir une chose essentielle : global agit au niveau du module, pas au niveau de la logique métier. Quand on confond les deux, on obtient du code qui fonctionne « par chance » jusqu’au moment où la base grossit, où les tests arrivent, ou où un second développeur modifie le même fichier. C’est là que les pièges deviennent visibles.
Les pièges qui cassent le plus souvent les scripts
Le premier piège est syntaxique : si vous utilisez un nom avant sa déclaration global dans le même bloc, Python peut lever une erreur de syntaxe ou refuser la logique. Le second est conceptuel : une affectation dans une fonction transforme un nom en variable locale, même si le même nom existe déjà au niveau du module.
Le troisième piège, que je vois souvent dans les scripts un peu pressés, consiste à confondre lecture, mutation et réaffectation. Une liste globale que l’on modifie avec append() n’a pas le même comportement qu’une variable qu’on réassigne à une nouvelle valeur. Cette différence semble technique, mais elle change directement la maintenabilité du programme.
- Évitez de faire dépendre deux fonctions d’un même état caché si une valeur de retour suffit.
- Ne surchargez pas des noms usuels comme
list,openouglobals. - Gardez à l’esprit que
globals()sert à inspecter le dictionnaire du module, pas à contourner proprement la portée. - Avec du code multithread ou asynchrone, l’état partagé devient plus fragile, surtout si plusieurs tâches peuvent l’écrire en même temps.
- Les blocs exécutés via
exec()ou du code généré dynamiquement ne partagent pas magiquement les mêmes règles de portée que le reste du fichier.
Je considère aussi que les variables globales rendent les tests plus difficiles à isoler. Un test qui modifie un état de module peut influencer le test suivant si la réinitialisation n’est pas impeccable. À partir de là, la vraie question n’est plus « puis-je utiliser global ? », mais plutôt « est-ce la bonne abstraction pour ce que je veux faire ? ». Et, très souvent, la réponse est non.
Quand éviter l’état global et quoi utiliser à la place
Dans les petits scripts, global peut dépanner. Dans une application, un service ou un projet amené à évoluer, je le réserve à des cas très limités. Dès qu’un état commence à porter de la logique, je préfère le faire passer par une structure plus explicite.
- Utilisez des paramètres et des valeurs de retour quand la fonction transforme une donnée.
- Utilisez une classe ou une
dataclassquand plusieurs fonctions partagent le même état métier. - Utilisez
nonlocalquand une fermeture doit conserver un état interne local au composant. - Utilisez une constante de module pour une valeur vraiment stable, pas pour un état qui change.
- Utilisez un cache dédié ou
functools.lru_cacheplutôt qu’un bricolage global pour mémoriser des résultats.
from dataclasses import dataclass
@dataclass
class Compteur:
valeur: int = 0
def incrementer(self):
self.valeur += 1
return self.valeurCe type de structure est souvent plus facile à relire qu’une variable globale disséminée dans plusieurs fonctions. On sait où l’état vit, qui le modifie et à quel moment. En pratique, c’est ce qui réduit le plus les bugs de maintenance, bien plus qu’un simple « ça marche » en phase de prototype. C’est aussi la solution que je recommande quand un script commence à ressembler à une petite application.
La règle simple que j’applique avant d’écrire global
Avant d’écrire global, je me pose trois questions très concrètes : est-ce que je peux transmettre la valeur en paramètre, est-ce que je peux renvoyer un résultat proprement, et est-ce que cet état mérite d’être encapsulé dans un objet ? Si l’une de ces réponses est oui, je m’éloigne du global.
Je garde global pour des cas étroits, lisibles et contrôlés, comme un petit compteur de script ou un état de module vraiment simple. Dès qu’il faut coordonner plusieurs fonctions, gérer des tests fiables ou préparer une évolution future, je préfère un design explicite. En Python, ce choix n’est pas seulement une question de style : c’est souvent ce qui fait la différence entre un code qui tient dans le temps et un script qui devient pénible à faire évoluer.