dans cette leçon, vous allez comprendre où et quand vos variables existent en python, comment les fonctions imbriquées interagissent avec les variables extérieures, et comment utiliser (avec précaution) global et nonlocal.
objectifs
- connaître les types de portée : locale, englobante, globale, intégrée (LEGB)
- comprendre l’ombre (shadowing) de variables
- utiliser
nonlocalpour modifier une variable dans une fonction englobante - créer et utiliser des closures
règle LEGB
ordre de recherche d’un nom :
- Local : variables définies dans la fonction en cours
- Enclosing : variables dans les fonctions englobantes (closures)
- Global : variables définies au niveau du module
- Built-in : noms fournis par python (
len,print, etc.)
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
outer() # affiche "local"
shadowing : masquer un nom
si une variable locale porte le même nom qu’une variable globale, la globale est masquée.
val = 42
def test():
val = 99
print(val)
test() # 99
print(val) # 42
global : modifier une variable globale
count = 0
def increment():
global count
count += 1
increment()
print(count)
⚠ évitez de modifier des globales, cela rend le code moins prévisible.
nonlocal : modifier une variable englobante non globale
def outer():
x = 0
def inner():
nonlocal x
x += 1
return x
return inner
compteur = outer()
print(compteur()) # 1
print(compteur()) # 2
closures : fonctions qui capturent un contexte
une closure se produit lorsqu’une fonction interne capture des variables de son environnement et les “mémorise” même après la fin de l’exécution de la fonction englobante.
def make_multiplier(facteur):
def multiplier(n):
return n * facteur
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
ici, facteur est conservé dans la closure de multiplier.
inspecter une closure
print(double.__closure__) # tuple de cellules capturées
print(double.__closure__[0].cell_contents) # 2
bonnes pratiques
- évitez
globalsauf cas très simples (ex: compteur rapide en script) - utilisez
nonlocalseulement si la variable doit vivre dans l’enclos fonctionnel - préférez retourner une valeur plutôt que modifier une variable externe
- closures : utiles pour fabriquer des fonctions spécialisées (fabrication de callbacks, paramètres pré-configurés)
mini exemples exécutables
# compteur avec closure
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
c1 = make_counter()
print(c1(), c1(), c1()) # 1 2 3
# générateur de filtres
def make_filter(min_value):
def filtrer(seq):
return [x for x in seq if x >= min_value]
return filtrer
filtre10 = make_filter(10)
print(filtre10([5, 10, 15]))
# usage global (à éviter en vrai)
flag = False
def toggle():
global flag
flag = not flag
toggle()
print(flag)
pièges courants
- oublier
nonlocal→ création d’une variable locale au lieu de modifier celle de l’enclos - réutiliser des noms built-in (
list,id,type) → masque la fonction intégrée - abuser de
global→ rend le code difficile à tester - mutable dans closure : toutes les fonctions créées partagent la même référence
exercices
make_adder(n)→ retourne une fonction qui ajoutenà son argument.make_accumulator()→ retourne une fonction qui additionne les valeurs qu’on lui donne et garde la somme en mémoire.- réécrire un compteur avec
globalau lieu denonlocal(observer la différence). - créer
make_power(exp)qui retourne une fonction élevant un nombre àexp. - expliquer pourquoi la variable capturée dans une closure conserve sa valeur même après la fin de la fonction englobante.