2.16. Portée¶
Lorsque Python recherche un nom à l’intérieur d’une fonction, il parcourt une séquence précise de portées. Comprendre cette séquence explique pourquoi certaines affectations masquent des noms externes, pourquoi d’autres les modifient, et pourquoi des fonctions imbriquées peuvent se souvenir de valeurs de l’endroit où elles ont été définies.
La recherche de nom commence dans la portée locale de la fonction et progresse vers l’extérieur, vers les portées de module et des fonctions intégrées, jusqu’à trouver une correspondance.¶
2.16.1. Portée locale et portée de module¶
Les noms définis à l’intérieur d’une fonction sont locaux à cette fonction et disparaissent à la fin de l’appel :
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Les noms définis au niveau supérieur d’un fichier .py sont de niveau module (parfois appelés globaux) et sont visibles partout dans ce fichier, y compris à l’intérieur des fonctions :
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
La lecture d’un nom de niveau module depuis l’intérieur d’une fonction est automatique. L”affectation à ce nom depuis l’intérieur d’une fonction ne l’est pas – l’affectation crée une nouvelle variable locale qui masque celle de niveau module pour le reste de l’appel :
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
Le counter à gauche fait de counter un nom local dans bump, de sorte que la lecture à droite n’a aucune valeur à trouver.
2.16.1.1. Le mot-clé global¶
Pour réellement réaffecter un nom de niveau module depuis l’intérieur d’une fonction, déclarez-le d’abord global :
counter = 0
def bump():
global counter
counter += 1
Recourez à global avec parcimonie. Les fonctions qui modifient un état caché sont plus difficiles à raisonner que les fonctions qui reçoivent des valeurs en arguments et renvoient de nouvelles valeurs. La solution habituelle au besoin de « partager un état » est de passer un objet (une liste, un dict, une instance de classe) en argument et de modifier celui-ci à la place.
2.16.2. Lambdas¶
Un lambda construit une petite fonction anonyme en une seule expression :
square = lambda x: x * x
square(7) # 49
C’est exactement équivalent à
def square(x):
return x * x
Le corps d’un lambda doit être une seule expression – pas d’instructions, pas de lignes multiples. Le principal usage est de passer une fonction minuscule en argument à quelque chose qui attend une fonction :
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Lorsque le corps dépasse une seule expression, passez à un vrai def. Nommer une fonction avec def lui donne aussi un nom dans les traces d’appel, ce qu’un lambda n’a pas.
2.16.3. Fermetures (closures)¶
Une fonction définie à l’intérieur d’une autre fonction peut lire des noms de la portée de la fonction englobante. La fonction interne capture ces noms et continue de fonctionner même après le retour de l’appel externe :
def make_adder(n):
def add(x):
return x + n
return add
add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))
Sortie
105 110
add5 et add10 sont deux fonctions distinctes, chacune se souvenant de son propre n. Une fonction qui construit et renvoie ainsi une fonction interne personnalisée est appelée une fermeture (closure). C’est la principale raison pour laquelle un langage a besoin de fonctions imbriquées en premier lieu – un moyen d’intégrer un état dans une valeur de fonction, puis de transmettre cette valeur comme un unique appelable.
La lecture des noms capturés se fait automatiquement. Leur réaffectation nécessite un mot-clé supplémentaire. L’exemple ci-dessous fait ce qui semble correct et échoue :
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
L’affectation à count à l’intérieur de tick rend count local à tick, de la même manière qu’elle l’aurait rendu local dans une fonction de niveau supérieur. Le mot-clé nonlocal indique à Python « ce nom vit dans la fonction englobante, réaffecte-le là-bas » :
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Sortie
1 2 3
nonlocal est à la portée de la fonction englobante ce que global est à la portée de module. Notez que modifier un objet capturé (appeler some_list.append(...), some_dict[k] = v) ne nécessite pas nonlocal – le nom n’est pas réaffecté, seul l’objet qu’il désigne est modifié.