Cours
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 bound
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
ouclass
comme noms.
- Veuillez éviter de masquer les fonctions intégrées telles que
list
,dict
,sum
oumax
.
- 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 literal
True = "yes" # cannot assign to a keyword
# SyntaxError: cannot assign to True
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.
def show_order_id():
order_id = 42
print("inside function:", order_id)
show_order_id()
print("outside function:", order_id) # NameError
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)
def price_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 function
return int(amount_cents * (1 - discount_rate))
# UnboundLocalError: cannot access local variable 'discount_rate' where it is not associated with a value
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
`.
def make_step_counter():
count = 0 # enclosing scope for 'increment'
def increment():
nonlocal count # rebind the 'count' in the nearest enclosing function
count += 1
return count
return increment
step = make_step_counter()
print(step()) # 1
print(step()) # 2
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"
def greet_city(city_name):
print(greeting, city_name) # reads global
def set_greeting(new_greeting):
global greeting
greeting = new_greeting # rebinds global
greet_city("Nairobi") # Hello Nairobi
set_greeting("Hi")
greet_city("Nairobi") # Hi Nairobi
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' constructor
list("abc") # TypeError: 'list' object is not callable
del list # fix by deleting the shadowing name
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.
if True:
status = "ready"
print(status) # "ready"
for i in range(3):
pass
print(i) # 2 (the last value from the loop)
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" in globals() or "x" in locals()) # False
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.08
def configure_tax(rate):
global tax_rate
tax_rate = float(rate)
def total_with_tax(cents):
return int(cents * (1 + tax_rate))
configure_tax(0.10)
print(total_with_tax(1000)) # 1100
En utilisant le nonlocal
Mot-clé
Veuillez utiliser nonlocal
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.
def make_accumulator(start=0):
total = start
def add(amount):
nonlocal total
total += amount
return total
return add
acc = make_accumulator()
print(acc(5)) # 5
print(acc(10)) # 15
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 :
def probe():
project = "alpha"
snap1 = locals()
snap1["project"] = "beta" # edits the snapshot only
observed = project # still "alpha"
snap2 = locals() # new snapshot
return snap1["project"], observed, snap2["project"]
print(probe()) # ('beta', 'alpha', 'alpha')
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 !