Portée des variables Python et explication de la règle LEGB
Veuillez découvrir ce que sont les portées de variables et vous familiariser avec la règle « LEGB ». Vous serez également confronté à des scénarios où vous pourrez observer les mots-clés globaux et non locaux en action.
Il est frustrant de voir apparaître les messages « nom non défini » ou « variable locale référencée avant attribution » immédiatement après avoir écrit quelques lignes. Le code semble correct, mais Python n'est pas d'accord. La cause principale est presque toujours la portée : où un nom est visible et à quel objet il fait référence à ce moment-là.
Une fois que vous comprenez le fonctionnement de la recherche de noms en Python, souvent résumé par la règle LEGB, ces erreurs deviennent prévisibles et faciles à corriger. Dans ce tutoriel, je vais vous présenter étape par étape les variables et les portées en Python.
Qu'est-ce qu'une variable ?
Une variable est un nom associé à un objet. En Python, vous créez une liaison avec l'opérateur d'affectation =. Les noms ne sont pas saisis ; ils peuvent faire référence à n'importe quel objet (chaîne, entier, liste, fonction, etc.).
customer_name ="Ava Torres"
order_count =1
total_cents =1+2*300# evaluated first, then boundPropulsé par
Veuillez respecter ces règles et conventions lors de la dénomination des variables afin d'éviter les erreurs de syntaxe et les bugs subtils.
Veuillez utiliser uniquement des lettres, des chiffres et des traits de soulignement ; les noms ne peuvent pas commencer par un chiffre.
Veuillez éviter d'utiliser des mots-clés tels que True, for ou class comme noms.
Veuillez éviter de masquer les fonctions intégrées telles que list, dict, sum ou max.
Il est préférable d'utiliser « snake_case » pour une meilleure lisibilité (PEP 8).
Ces exemples illustrent ce qu'il convient d'éviter et les erreurs générées par Python :
first string value ="First string"# spaces not allowed# SyntaxError: invalid syntax
1st_value =10# cannot start with a digit# SyntaxError: invalid decimal literalTrue="yes"# cannot assign to a keyword# SyntaxError: cannot assign to TruePropulsé par
Fonctionnement de la portée en Python (LEGB)
La portée est la zone du code dans laquelle un nom est visible. Lorsque vous utilisez un nom, Python le recherche dans cet ordre (LEGB) :
Local: la portée de la fonction actuelle.
Enclosing: toutes les portées de fonction externes (pour les fonctions imbriquées).
Global: niveau supérieur du module (espace de noms du module).
Intégrés: noms définis par Python dans builtins (par exemple, len, print).
Les noms sont stockés dans des espaces de noms (considérez-les comme des dictionnaires associant des noms à des objets). La portée concerne les espaces de noms que Python consulte pour résoudre un nom à un moment donné dans le code.
Portée locale
Les noms attribués à l'intérieur d'une fonction sont locaux à cette fonction, sauf indication contraire. Ils ne sont pas accessibles en dehors de la fonction.
defshow_order_id():
order_id =42print("inside function:", order_id)
show_order_id()print("outside function:", order_id)# NameErrorPropulsé par
Python détermine la portée lors de la compilation. Si une fonction assigne une valeur à un nom n'importe où dans son corps, toutes les utilisations de ce nom dans la fonction sont considérées comme locales, ce qui conduit à une erreur courante d'UnboundLocalError lorsque vous lisez avant d'assigner :
discount_rate =0.10# module-level (global)defprice_with_discount(amount_cents):print("configured discount:", discount_rate)# looks local because of the assignment below
discount_rate =0.20# assignment makes 'discount_rate' local in this functionreturnint(amount_cents *(1- discount_rate))# UnboundLocalError: cannot access local variable 'discount_rate' where it is not associated with a valuePropulsé par
Pour utiliser le nom au niveau du module dans la fonction, veuillez soit éviter de lui attribuer une valeur, soit le marquer comme « global » (voir ci-dessous).
Portée englobante (fermetures)
Les fonctions imbriquées peuvent accéder aux noms de la fonction qui les englobe immédiatement. Pour lier à nouveau un tel nom (et non simplement le lire), veuillez le déclarer comme suit : ` nonlocal`.
defmake_step_counter():
count =0# enclosing scope for 'increment'defincrement():nonlocal count # rebind the 'count' in the nearest enclosing function
count +=1return count
return increment
step = make_step_counter()print(step())# 1print(step())# 2Propulsé par
Sans l'instruction ` nonlocal`, l'affectation à ` count ` à l'intérieur de ` increment() ` créerait un nouveau nom local et laisserait le nom externe ` count ` inchangé.
Portée mondiale
Les noms attribués au niveau supérieur d'un module sont stockés dans l'espace de noms global du module. Toute fonction peut les lire. Pour attribuer un nom au niveau du module à partir d'une fonction, veuillez le déclarer à l'intér global.
greeting ="Hello"defgreet_city(city_name):print(greeting, city_name)# reads globaldefset_greeting(new_greeting):global greeting
greeting = new_greeting # rebinds global
greet_city("Nairobi")# Hello Nairobi
set_greeting("Hi")
greet_city("Nairobi")# Hi NairobiPropulsé par
Veuillez utiliser l'global e avec modération. Il est préférable de transmettre des valeurs en tant que paramètres et de renvoyer des résultats afin de garantir la testabilité et la prévisibilité du code.
Portée intégrée (et remarque sur les mots-clés)
La portée intégrée contient des noms tels que len, print et Exception. Veuillez éviter de les masquer, sinon vous perdrez l'accès à la fonction intégrée pour cette portée.
list=[1,2,3]# shadows the built-in 'list' constructorlist("abc")# TypeError: 'list' object is not callabledellist# fix by deleting the shadowing namePropulsé par
Les mots-clés (tels que if, for, def) font partie de la syntaxe Python, et non de l'espace de noms intégré, et ne peuvent jamais être utilisés comme identifiants.
Blocs, boucles et compréhensions
Les instructions de bloc et les compréhensions Python ont des comportements de portée spécifiques qui surprennent souvent les développeurs provenant d'autres langages.
Il n'existe pas de portée de bloc pour if/for/while/with
Les affectations à l'intérieur de ces blocs affectent la portée qui les contient (fonction ou module). Les variables de boucle restent également définies après la fin de la boucle.
ifTrue:
status ="ready"print(status)# "ready"for i inrange(3):passprint(i)# 2 (the last value from the loop)Propulsé par
Les compréhensions isolent leur variable d'itération.
Les compréhensions de listes, de dictionnaires et d'ensembles ont leur propre portée locale pour les variables de boucle. La variable d'itération ne fuit pas dans la portée environnante.
numbers =[1,2,3][x for x in numbers]print("x"inglobals()or"x"inlocals())# FalsePropulsé par
Python 3.12 (PEP 709) a intégré les compréhensions pour améliorer la vitesse tout en conservant cette isolation ; vous bénéficiez toujours de variables de boucle claires et sans fuite, avec une exécution plus rapide.
En utilisant le global Mot-clé
Déclarez un nom comme global à l'intérieur d'une fonction lorsque vous avez besoin de relier une variable au niveau du module. Veuillez placer la déclaration près du début de la fonction afin qu'il soit clair quelle variable vous modifiez.
tax_rate =0.08defconfigure_tax(rate):global tax_rate
tax_rate =float(rate)deftotal_with_tax(cents):returnint(cents *(1+ tax_rate))
configure_tax(0.10)print(total_with_tax(1000))# 1100Propulsé par
En utilisant le nonlocalMot-clé
Veuillez utilisernonlocal pour redéfinir un nom à partir de la portée de fonction englobante la plus proche. Ceci est courant dans les fermetures qui conservent l'état.
defmake_accumulator(start=0):
total = start
defadd(amount):nonlocal total
total += amount
return total
return add
acc = make_accumulator()print(acc(5))# 5print(acc(10))# 15Propulsé par
L'utilisation de l'instruction « nonlocal » pour un nom non défini dans une fonction englobante constitue une erreur de type « SyntaxError ». Si le nom est global, veuillez utiliser global à la place.
locals() et globals() en 2025
Ces fonctionssont utiles pour l'inspection et le débogage, mais ne doivent pas être utilisées pour mettre à jour des variables. À partir de Python 3.13 (PEP 667), chaque appel à locals() dans une fonction renvoie un instantané indépendant. La modification de cet instantané n'affecte pas les variables locales réelles. J'ai confirmé ce comportement en exécutant l'extrait de code suivant sur Python 3.13 :
defprobe():
project ="alpha"
snap1 =locals()
snap1["project"]="beta"# edits the snapshot only
observed = project # still "alpha"
snap2 =locals()# new snapshotreturn snap1["project"], observed, snap2["project"]print(probe())# ('beta', 'alpha', 'alpha')Propulsé par
Veuillez utiliser globals() de manière similaire pour l'espace de noms du module. Lors de la rédaction du code d'une application, privilégiez les paramètres explicites et les valeurs de retour plutôt que les recherches dynamiques.
Erreurs courantes et solutions pour les résoudre
Ces erreurs représentent la plupart des erreurs liées à la portée que j'observe dans la pratique.
UnboundLocalError à partir de l'affectation dans une fonction : Python considère un nom comme local s'il est attribué n'importe où dans la fonctionon. Veuillez déplacer la lecture après l'affectation, renommer ou ajouter global/nonlocal selon le cas.
Attente d'une portée de bloc : Les noms attribués dans les instructions if/for/while/with persistent dans la portée qui les contient. Utilisez des fonctions d'aide plus strictes pour restreindre la portée.
Ombres portées intégrées :Veuillez éviter les noms d'identifiants tels que « list », « dict », « sum », « id » ou « min ». Privilégiez des noms descriptifs tels que « customer_list » ou « min_allowed ».
Omission de l'nonlocal e dans les fermetures : Si vous avez l'intention de mettre à jour une variable de fonction externe, veuillez la déclarer comme non locale. Sinon, vous créez une nouvelle variable locale et la valeur externe ne change pas.
Confusion entre les mots-clés et les fonctions intégrées : Les mots-clés (syntaxe) ne peuvent en aucun cas être utilisés comme noms. Les éléments encastrés peuvent être masqués, mais cela n'est pas recommandé.
Meilleures pratiques pour simplifier la portée
Ces pratiques facilitent la lecture, le test et le débogage du code.
Privilégiez les fonctions restreintes et ciblées. Définissez les variables dans la portée la plus étroite possible.
Transmettre les données via des paramètres et des valeurs de retour. Réduisez au minimum l'état au niveau du module.
Veuillez utiliser les fermetures de manière intentionnelle. Veuillez documenter le nom externe qu', une fonction imbriquée, capture, et utilisez nonlocal lorsque vous avez réellement besoin de le relier.
Veuillez choisirdes noms descriptifset non conflictuels. Un trait de soulignement en début de nom (par exemple, _cache) indique une utilisation interne.
Considérezlocals()/globals() comme des diagnostics en lecture seule, et non comme un mécanisme de configuration.
Conclusion
Python résout les noms en recherchant dans les portées locales → englobantes → globales → intégrées. Comprendre cet ordre, et savoir quand utiliser global ou non local, permet d'éviter des erreurs courantes telles que NameError et UnboundLocalError. Limitez les variables au champ d'application le plus restreint possible, évitez de masquer les fonctions intégrées et utilisez les fermetures de manière réfléchie. Avec ce modèle mental, vos fonctions se comportent de manière prévisible et la portée cesse d'être une source de surprises.
J'ai travaillé dans différents secteurs d'activité et j'ai porté plusieurs casquettes : développeur de logiciels, chercheur en apprentissage automatique, scientifique des données, chef de produit. Mais au fond, je suis un programmeur qui aime apprendre et partager ses connaissances !
Découvrez les fonctions lambda Python, leur utilité et quand les utiliser. Comprend des exemples pratiques et des bonnes pratiques pour une mise en œuvre efficace.
Tutoriel complet d'introduction aux boucles Python. Apprenez et pratiquez les boucles while et for, les boucles imbriquées, les mots-clés break et continue, la fonction range et bien plus encore.
Initiez-vous aux structures de données de Python : apprenez-en plus sur les types de données et les structures de données primitives et non primitives, telles que les chaînes de caractères, les listes, les piles, etc.
Il est frustrant de voir apparaître les messages « nom non défini » ou « variable locale référencée avant attribution » immédiatement après avoir écrit quelques lignes. Le code semble correct, mais Python n'est pas d'accord. La cause principale est presque toujours la portée : où un nom est visible et à quel objet il fait référence à ce moment-là.
Une fois que vous comprenez le fonctionnement de la recherche de noms en Python, souvent résumé par la règle LEGB, ces erreurs deviennent prévisibles et faciles à corriger. Dans ce tutoriel, je vais vous présenter étape par étape les variables et les portées en Python.
Qu'est-ce qu'une variable ?
Une variable est un nom associé à un objet. En Python, vous créez une liaison avec l'opérateur d'affectation
=. Les noms ne sont pas saisis ; ils peuvent faire référence à n'importe quel objet (chaîne, entier, liste, fonction, etc.).Veuillez respecter ces règles et conventions lors de la dénomination des variables afin d'éviter les erreurs de syntaxe et les bugs subtils.
True,forouclasscomme noms.list,dict,sumoumax.snake_case» pour une meilleure lisibilité (PEP 8).Ces exemples illustrent ce qu'il convient d'éviter et les erreurs générées par Python :
Fonctionnement de la portée en Python (
LEGB)La portée est la zone du code dans laquelle un nom est visible. Lorsque vous utilisez un nom, Python le recherche dans cet ordre (LEGB) :
builtins(par exemple,len,print).Les noms sont stockés dans des espaces de noms (considérez-les comme des dictionnaires associant des noms à des objets). La portée concerne les espaces de noms que Python consulte pour résoudre un nom à un moment donné dans le code.
Portée locale
Les noms attribués à l'intérieur d'une fonction sont locaux à cette fonction, sauf indication contraire. Ils ne sont pas accessibles en dehors de la fonction.
Python détermine la portée lors de la compilation. Si une fonction assigne une valeur à un nom n'importe où dans son corps, toutes les utilisations de ce nom dans la fonction sont considérées comme locales, ce qui conduit à une erreur courante d'
UnboundLocalErrorlorsque vous lisez avant d'assigner :Pour utiliser le nom au niveau du module dans la fonction, veuillez soit éviter de lui attribuer une valeur, soit le marquer comme «
global» (voir ci-dessous).Portée englobante (fermetures)
Les fonctions imbriquées peuvent accéder aux noms de la fonction qui les englobe immédiatement. Pour lier à nouveau un tel nom (et non simplement le lire), veuillez le déclarer comme suit : `
nonlocal`.Sans l'instruction `
nonlocal`, l'affectation à `count` à l'intérieur de `increment()` créerait un nouveau nom local et laisserait le nom externe `count` inchangé.Portée mondiale
Les noms attribués au niveau supérieur d'un module sont stockés dans l'espace de noms global du module. Toute fonction peut les lire. Pour attribuer un nom au niveau du module à partir d'une fonction, veuillez le déclarer à l'intér
global.Veuillez utiliser l'
globale avec modération. Il est préférable de transmettre des valeurs en tant que paramètres et de renvoyer des résultats afin de garantir la testabilité et la prévisibilité du code.Portée intégrée (et remarque sur les mots-clés)
La portée intégrée contient des noms tels que
len,printetException. Veuillez éviter de les masquer, sinon vous perdrez l'accès à la fonction intégrée pour cette portée.Les mots-clés (tels que
if,for,def) font partie de la syntaxe Python, et non de l'espace de noms intégré, et ne peuvent jamais être utilisés comme identifiants.Blocs, boucles et compréhensions
Les instructions de bloc et les compréhensions Python ont des comportements de portée spécifiques qui surprennent souvent les développeurs provenant d'autres langages.
Il n'existe pas de portée de bloc pour
if/for/while/withLes affectations à l'intérieur de ces blocs affectent la portée qui les contient (fonction ou module). Les variables de boucle restent également définies après la fin de la boucle.
Les compréhensions isolent leur variable d'itération.
Les compréhensions de listes, de dictionnaires et d'ensembles ont leur propre portée locale pour les variables de boucle. La variable d'itération ne fuit pas dans la portée environnante.
Python 3.12 (PEP 709) a intégré les compréhensions pour améliorer la vitesse tout en conservant cette isolation ; vous bénéficiez toujours de variables de boucle claires et sans fuite, avec une exécution plus rapide.
En utilisant le
globalMot-cléDéclarez un nom comme
globalà l'intérieur d'une fonction lorsque vous avez besoin de relier une variable au niveau du module. Veuillez placer la déclaration près du début de la fonction afin qu'il soit clair quelle variable vous modifiez.En utilisant le
nonlocalMot-cléVeuillez utiliser
nonlocalpour redéfinir un nom à partir de la portée de fonction englobante la plus proche. Ceci est courant dans les fermetures qui conservent l'état.L'utilisation de l'instruction «
nonlocal» pour un nom non défini dans une fonction englobante constitue une erreur de type «SyntaxError». Si le nom est global, veuillez utiliserglobalà la place.locals()etglobals()en 2025Ces fonctionssont utiles pour l'inspection et le débogage, mais ne doivent pas être utilisées pour mettre à jour des variables. À partir de Python 3.13 (PEP 667), chaque appel à locals() dans une fonction renvoie un instantané indépendant. La modification de cet instantané n'affecte pas les variables locales réelles. J'ai confirmé ce comportement en exécutant l'extrait de code suivant sur Python 3.13 :
Veuillez utiliser
globals()de manière similaire pour l'espace de noms du module. Lors de la rédaction du code d'une application, privilégiez les paramètres explicites et les valeurs de retour plutôt que les recherches dynamiques.Erreurs courantes et solutions pour les résoudre
Ces erreurs représentent la plupart des erreurs liées à la portée que j'observe dans la pratique.
UnboundLocalErrorà partir de l'affectation dans une fonction : Python considère un nom comme local s'il est attribué n'importe où dans la fonctionon. Veuillez déplacer la lecture après l'affectation, renommer ou ajouter global/nonlocal selon le cas.customer_list» ou «min_allowed».nonlocale dans les fermetures : Si vous avez l'intention de mettre à jour une variable de fonction externe, veuillez la déclarer comme non locale. Sinon, vous créez une nouvelle variable locale et la valeur externe ne change pas.Meilleures pratiques pour simplifier la portée
Ces pratiques facilitent la lecture, le test et le débogage du code.
_cache) indique une utilisation interne.Conclusion
Python résout les noms en recherchant dans les portées locales → englobantes → globales → intégrées. Comprendre cet ordre, et savoir quand utiliser global ou non local, permet d'éviter des erreurs courantes telles que
NameErroretUnboundLocalError. Limitez les variables au champ d'application le plus restreint possible, évitez de masquer les fonctions intégrées et utilisez les fermetures de manière réfléchie. Avec ce modèle mental, vos fonctions se comportent de manière prévisible et la portée cesse d'être une source de surprises.