La portée (Scope) : Local, Enclosing, Global et Built-in (LEGB)
- Expliquer ce qu’est la portée (Scope) et pourquoi une variable créée dans une fonction n’existe pas à l’extérieur
- Connaître les quatre portées — Local, Enclosing, Global et Built-in — et l’ordre de recherche LEGB
- Comprendre ce qui se passe quand on modifie une variable globale dans une fonction, et ce que fait
global - Écrire des fonctions qui reçoivent des données par des paramètres et renvoient un résultat, au lieu de dépendre de variables globales
Qu’allons-nous apprendre ?
La portée (Scope) = la zone du code où un nom (une variable ou une fonction) est connu. Nous allons apprendre :
- Local — les variables dans une fonction.
- Enclosing — les variables d’une fonction extérieure, quand il y a une fonction dans une fonction.
- Global — les variables au niveau supérieur du fichier.
- Built-in — les noms intégrés de Python (
print,len…). - LEGB — l’ordre dans lequel Python cherche un nom.
Pourquoi en avons-nous besoin ?
Si chaque variable était accessible de partout, une variable nommée total dans une fonction écraserait une variable total dans une autre fonction, et dans un programme de 10 000 lignes ce serait le chaos. La portée isole chaque fonction : elle peut utiliser ses propres noms sans craindre de collisions.
D’un autre côté, des erreurs comme NameError ou UnboundLocalError sont très déroutantes si l’on ne comprend pas la portée.
Explication à partir des bases
Local
Une variable créée dans une fonction (y compris ses paramètres) est locale :
- Elle est créée à chaque appel de la fonction, et disparaît quand elle se termine.
- Elle n’est pas accessible de l’extérieur.
- Deux fonctions différentes peuvent avoir des variables locales du même nom, sans aucun lien entre elles.
Global
Une variable créée au niveau supérieur du fichier (pas dans une fonction) est globale :
- Elle est accessible en lecture depuis toutes les fonctions du fichier.
- Pour la modifier (lui affecter une nouvelle valeur) depuis une fonction, il faut une déclaration explicite :
global name.
Enclosing
On peut définir une fonction dans une fonction. La fonction intérieure voit les variables de la fonction extérieure. La portée de la fonction extérieure s’appelle Enclosing (englobante) du point de vue de la fonction intérieure.
Built-in
Les noms que Python connaît toujours, sans import : print, len, sum, max, int, str, list, range…
L’ordre de recherche : LEGB
Quand le code utilise un nom, Python le cherche dans cet ordre, et s’arrête au premier endroit où il l’a trouvé :
L Local → inside the current function
E Enclosing → inside outer functions (if nested)
G Global → top level of the file
B Built-in → Python's built-in names
→ not found anywhere: NameErrorVous cherchez vos clés : d’abord dans votre poche (Local), puis dans le sac que vous portez (Enclosing), puis à la maison (Global), et enfin dans le hall de l’immeuble, où l’on laisse des choses pour tout le monde (Built-in). Dès que vous les trouvez, vous arrêtez de chercher. Et une clé dans la poche d’un ami, vous ne pouvez pas la voir du tout : c’est le Local d’une autre fonction.
Syntaxe
counter = 0 # global
def outer():
message = "hi" # local to outer = enclosing for inner
def inner():
result = message + "!" # local to inner; message found in Enclosing
return result
return inner()
def increase():
global counter # declare: assignments go to the GLOBAL counter
counter = counter + 1Premier exemple de code
week-2/scope_demo.py :
course = "Kodkod 6"
def welcome(name):
message = "Welcome " + name + " to " + course
return message
print(welcome("Dana"))
print(course)
print(message)Sortie :
Welcome Dana to Kodkod 6
Kodkod 6
Traceback (most recent call last):
File "scope_demo.py", line 11, in <module>
print(message)
NameError: name 'message' is not definedAnalyse du code ligne par ligne
| Nom | Portée | Explication |
|---|---|---|
course | Global | Créé au niveau supérieur. welcome le lit : L (pas trouvé) → E (aucun) → G (trouvé !). |
name | Local de welcome | Un paramètre = une variable locale. N’existe que pendant l’appel. |
message | Local de welcome | Créé dans la fonction et disparaît quand elle renvoie. |
print, len… | Built-in | Trouvés à la fin de la recherche. |
print(message) à l’extérieur | — | Au niveau supérieur, il n’y a pas de message. C’était un Local, et il a déjà disparu, d’où la NameError. |
La bonne façon de sortir une valeur d’une fonction est return, et c’est exactement ce que fait welcome. L’appelant a reçu la valeur (et peut la stocker dans sa propre variable).
Un autre exemple
Le même nom dans plusieurs portées, et global. week-2/scope_levels.py :
total = 100
def local_total():
total = 5
print("inside local_total:", total)
def read_global():
print("inside read_global:", total)
def change_global():
global total
total = total + 50
print("inside change_global:", total)
def outer():
label = "outer"
def inner():
print("inner sees enclosing label:", label)
inner()
local_total()
print("after local_total:", total)
read_global()
change_global()
print("after change_global:", total)
outer()Sortie :
inside local_total: 5
after local_total: 100
inside read_global: 100
inside change_global: 150
after change_global: 150
inner sees enclosing label: outerAnalyse :
local_total— l’affectationtotal = 5crée une nouvelle variable locale nomméetotal, qui « cache » la globale. La globale n’a pas changé (100).read_global— ne fait que liretotal. Elle n’a pas de variable locale de ce nom, donc selon LEGB on arrive à la globale.change_global—global totaldit à Python : « dans cette fonction,totalest la globale ». Maintenant l’affectation modifie la globale (150).innervoit lelabeldeouter. C’est le E de LEGB.
Erreur courante
Erreur 1 : UnboundLocalError — lire puis modifier une globale sans global.
count = 0
def add_one():
count = count + 1
add_one()UnboundLocalError: cannot access local variable 'count' where it is not associated with a valuePourquoi ? Parce que la fonction contient une affectation à count, Python décide (à l’avance, avant l’exécution) que count est local dans toute la fonction. Et alors count + 1 essaie de lire une variable locale qui n’a pas encore reçu de valeur.
Erreur 2 (la grande) : utiliser global comme solution permanente. global « fonctionne », mais :
- Chaque fonction peut modifier la variable, et quand il y a un bug, impossible de savoir qui l’a modifiée.
- La fonction dépend de quelque chose « venu de l’extérieur », il est donc difficile de la tester et de la réutiliser.
La façon propre : un paramètre entre, return sort.
def add_one(count):
return count + 1
count = 0
count = add_one(count)
print(count) # 1Maintenant, ce qui entre et ce qui sort est tout à fait clair, et la fonction ne touche à rien qu’elle n’a pas reçu. (Dans le thème POO, nous apprendrons une autre façon de gérer un « état » qui change : les objets.)
Erreur 3 : écraser un nom Built-in.
list = [1, 2, 3]
numbers = list(range(5))TypeError: 'list' object is not callableNotre list globale vient avant le Built-in dans LEGB, elle a donc « caché » la fonction intégrée. C’est la même chose avec sum = 0, max = 10, input = ..., str = .... Ne donnez jamais aux variables les noms des Built-ins.
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Exercice 1: La variable qui a disparu
def welcome(name):
message = "Welcome " + name
return message
print(welcome("Dana"))
print(message)- Que se passera-t-il à la dernière ligne, et pourquoi ?
- Corrigez pour que le message soit affiché deux fois.
Afficher la solution
NameError: name 'message' is not defined.messageest une variable Local dewelcome— elle est créée à chaque appel et disparaît quand la fonction se termine.- Enregistrez la valeur renvoyée dans notre propre variable :
def welcome(name):
message = "Welcome " + name
return message
result = welcome("Dana")
print(result)
print(result)Welcome Dana
Welcome DanaExercice 2: Prédire la sortie
x = "global"
def first():
x = "local in first"
print("1:", x)
def second():
print("2:", x)
first()
second()
print("3:", x)Afficher la solution
x = "global"
def first():
x = "local in first"
print("1:", x)
def second():
print("2:", x)
first()
second()
print("3:", x)1: local in first
2: global
3: globalfirstcrée son proprexlocal — la globale n’a pas été touchée.secondne définit pasx, donc selon LEGB on arrive à la Global.
Exercice 3: UnboundLocalError
count = 0
def add_one():
count = count + 1
add_one()- Quelle erreur obtiendrez-vous, et pourquoi ?
- Corrigez sans
global.
Afficher la solution
UnboundLocalError. À cause de l’affectationcount = ..., Python décide quecountest une variable locale dans toute la fonction — et alorscount + 1essaie de lire une variable locale qui n’a pas encore de valeur.- Un paramètre entre,
returnsort :
def add_one(count):
return count + 1
count = 0
count = add_one(count)
count = add_one(count)
print(count)2Exercice 4: Écraser un nom intégré
list = [1, 2, 3]
numbers = list(range(5))- Quelle erreur obtiendrez-vous ?
- Expliquez avec LEGB pourquoi cela arrive, et corrigez.
Afficher la solution
TypeError: 'list' object is not callable.- Selon LEGB, Python cherche
listdans Global avant Built-in. Notre variable nomméelista « caché » la fonction intégrée. La correction — ne pas donner aux variables les noms des Built-ins :
items = [1, 2, 3]
numbers = list(range(5))
print(items, numbers)[1, 2, 3] [0, 1, 2, 3, 4]Exercice 5: Sans global
cart = []
total = 0
def add(price):
global total
cart.append(price)
total = total + price
add(100)
add(50)
print(cart, total)Refactorisez pour qu’aucune fonction n’utilise global ni ne modifie de variables extérieures : chaque fonction reçoit ce dont elle a besoin par des paramètres et renvoie un résultat.
Résultat attendu :
[100, 50] 150Afficher la solution
def add(cart, price):
return cart + [price]
def cart_total(cart):
return sum(cart)
cart = []
cart = add(cart, 100)
cart = add(cart, 50)
print(cart, cart_total(cart))[100, 50] 150totaln’est plus un « état » à maintenir — il est calculé à partir du panier.addrenvoie une nouvelle liste (cart + [price]) et ne modifie pas l’existante.
Résumé
- La portée = là où un nom est connu. Une variable créée dans une fonction (y compris les paramètres) est Local, et disparaît à la fin de l’appel.
- LEGB : Local → Enclosing → Global → Built-in. On s’arrête au premier endroit où il est trouvé. S’il n’est pas trouvé,
NameError. - Une affectation dans une fonction crée une variable locale, même s’il existe une globale du même nom.
global xpermet de modifier une globale depuis une fonction, mais il vaut mieux l’éviter. Un paramètre entre etreturnsort.- Ne donnez pas aux variables les noms des Built-ins (
list,sum,max,str…).