Définition
Écrivez une fonction et donnez à l'une de ses variables un nom déjà pris ailleurs dans le fichier, un total, un compteur. Vont-elles se marcher dessus ? C'est la question à laquelle répond la portée : la région du programme où un nom désigne une chose précise, avant de redevenir libre plus loin.
En Python, cette région ne se découpe pas ligne par ligne mais bloc par bloc. Chaque fonction, chaque classe et chaque fichier ouvre un espace de noms neuf : ce qui naît à l'intérieur y reste, et disparaît quand le bloc se termine.
message = "je viens du module"
def afficher():
message = "je viens de la fonction"
print(message)
afficher()
print(message)
# je viens de la fonction
# je viens du moduleLes deux message se ressemblent sans être la même chose. Celui de la fonction naît à l'appel et meurt au retour, sans jamais toucher celui du module. C'est cette étanchéité qui répond à la question de départ : vous choisissez le nom qui vous arrange, sans relire le fichier.
La règle LEGB
Une fonction n'est pourtant jamais totalement isolée : elle utilise aussi des noms qu'elle n'a pas créés elle-même, une constante du module, une fonction fournie par Python. Face à un de ces noms, comment sait-elle où le chercher ?
Python le cherche dans quatre espaces, toujours dans le même ordre, et s'arrête au premier qui répond. Les initiales de ces espaces forment l'acronyme LEGB. Le tableau suivant détaille ce que contient chaque niveau.
| Niveau | Ce qu'il contient | Exemple typique |
|---|---|---|
| Local | Les noms créés dans la fonction en cours | Un paramètre, une variable d'étape |
| Enclosing | Les noms de la fonction qui entoure celle-ci | Le compteur d'une closure |
| Global | Les noms du module, écrits sans indentation | Une constante de configuration |
| Built-in | Les noms fournis d'office par Python | len, print, sorted |
Le premier niveau qui répond gagne, sans que Python vérifie les suivants. Si les quatre restent muets, une NameError est levée.
Nommer une variable list, sum ou type fonctionne sans la moindre erreur, puisque Local est examiné avant Built-in. Mais la fonction intégrée devient invisible pour tout le reste du bloc : le premier appel à list(...) qui suit plante avec un TypeError, loin de la vraie cause.
Le schéma suivant visualise ce trajet : une lecture qui s'arrête au module, sans atteindre les noms intégrés.
Lire remonte, écrire reste sur place
La lecture d'un nom suit le trajet qu'on vient de décrire, du local vers les natifs. Mais qu'en est-il quand on écrit dans une variable ? On pourrait croire qu'une affectation va, comme une lecture, chercher le nom existant pour le modifier sur place.
Ce n'est pas ainsi que Python procède. Une affectation ne remonte jamais : elle crée ou remplace un nom dans le bloc où elle est écrite. Cette asymétrie explique, à elle seule, la quasi-totalité des surprises que la portée réserve.
total = 0
def ajouter(montant):
total = total + montant # UnboundLocalError
def ajouter_corrige(montant):
global total
total = total + montantLa ligne total = total + montant semble d'abord lire total, puis l'écrire. Mais Python lit tout le corps avant de l'exécuter : dès qu'il y repère une affectation à total, il décide que ce nom est local pour toute la fonction, y compris là où il le lit avant de l'écrire. D'où l'UnboundLocalError.
Deux mots-clés permettent malgré tout d'écrire vers l'extérieur : global pour le module, et nonlocal pour la fonction englobante. Y recourir reste rarement le meilleur choix : une fonction qui renvoie une valeur se relit mieux qu'une fonction qui modifie un état partagé.
Les blocs qui n'ouvrent pas de portée
Dans beaucoup de langages, une nouvelle portée s'ouvre à chaque accolade : une variable déclarée dans un if disparaît dès que le bloc se termine. En arrivant de ces langages, on suppose souvent que Python fait pareil.
Il ne fait pas pareil. Seuls les fonctions, les classes et les modules ouvrent une portée en Python. Une condition, une boucle ou un bloc de rattrapage d'erreur partagent la portée qui les contient, comme ici.
for client in clients:
remise = calculer(client)
print(client)
print(remise)Les deux noms restent lisibles après la boucle, ce qui est commode quand c'est voulu et redoutable quand ça ne l'est pas.
Si la liste est vide, la boucle ne s'exécute pas une seule fois : ni client ni remise ne sont créés, et la ligne suivante lève un NameError qui ne se manifeste que sur cette liste précise.
Seule exception à cette porosité : la variable d'une compréhension de liste reste enfermée dans la compréhension et ne déborde jamais sur la suite.
Questions fréquentes
Pourquoi déconseille-t-on le mot-clé global ?
Parce qu'il rend le résultat d'une fonction dépendant de l'état du module plutôt que de ses arguments : deux appels identiques peuvent alors donner deux réponses différentes. Renvoyer la valeur reste préférable dans la plupart des cas.
Le corps d'une classe est-il visible depuis ses méthodes ?
Non, et c'est une surprise fréquente. Le corps de la classe a bien sa propre portée, mais elle ne vit que le temps de la définition : une méthode ne la voit pas comme un niveau englobant. Un attribut se lit par self, jamais comme une variable ordinaire.
Comment savoir quels noms sont visibles à un endroit donné ?
Les fonctions intégrées locals() et globals() renvoient les noms accessibles là où elles sont appelées, ce qui tranche un doute sans lancer de débogueur.