Précédemment la notion de portée de variable et notamment la règle LEGB qui permet de trouver les variables que l'on référence ont été abordées. Une variable définie dans une fonction est locale à cette fonction. On présente ici comment modifier la portée des variables avec les instructions global et nonlocal. global permet de rendre une variable locale de portée globale, et nonlocal permet de rendre une variable locale de portée locale dans une fonction englobante.
# la variable *a* est définie au niveau # du module, sa portée est globale a = 'a globale' def f(): # une définition dans une fonction crée une # variable locale a = 'a locale' print(a) # une fois que la fonction retourne, la variable locale # et sa valeur n'existent plus # on affiche la valeur initiale de a >>> print(a) a globale # on appelle la fonction f >>> f() a locale # on vérifie la valeur de a >>> print(a) a globale
La variable *a* est définie au niveau du module, c'est une variable globale, elle référence la chaîne de caractères 'a globale'. La fonction *f* redéfinit localement la variable *a*, qui vaut 'a dans f' et l'affiche.
La variable locale *a* est affichée sauf que les variables locales, lorsque la fonction retourne, sont détruites: elles n'ont aucun impact sur les variables globales. Après appel de la fonction *f* la variable globale *a* vaut toujours 'a globale'.
Ici on souhaiterait pouvoir modifier une variable globale depuis une fonction, c'est possible en utilisant l'instruction global. Regardons comment cela fonctionne.
Pour reprendre l'exemple précédent, dans la fonction, on ajoute l'instruction *global a*, le reste du code reste inchangé:
# la variable *a* est définie au niveau # du module, sa portée est globale a = 'a globale' def f(): # on utilise la variable globale a global a # la variable globale est redéfinie a = 'a locale' print(a) # on affiche la valeur initiale de a >>> print(a) a globale # on appelle la fonction f >>> f() a locale # on vérifie la valeur de a modifiée implicitement >>> print(a) a locale
Dans la fonction *f*, on a ajouté l'instruction global a
avant toute affectation de *a*. On indique ainsi à l'interpréteur Python qu'on utilise la variable globale *a*. Ici, ça n'est même pas une référence partagée, on accède directement à la variable qui est définie dans le module. Donc c'est une manière, depuis une fonction, de modifier des variables globales.
Cependant, cet usage a un inconvénient majeur. Lorsque on modifie une variable globale à l'intérieur d'une fonction, on fait une modification implicite d'une variable globale. C'est syntaxiquement et sémantiquement correct mais un code contenant des modifications implicites est beaucoup plus complexe à appréhender et à maintenir.
# variable globale a = 10 # modification implicite # méthode déconseillée def f(): global a a = a + 10 >>> print(a) 10 # l'appel de f altère implicitement # la variable globale *a* >>> def() >>> print(a) 20
Dans l'exemple ci dessus, l'accès à la variable globale est fait de manière implicite, et la modification de la variable globale est faite de manière implicite également. Sans lire le détail du fonctionnement de la fonction *f* rien ne nous indique clairement que la valeur de la variable globale *a* est altérée.
On peut modifier ce code pour rendre les modifications sur la variable globale explicites.
# variable globale a = 10 # Pour produire un code plus facilement maintenable # on peut par exemple écrire cette fonction def add_10( x ): return x + 10 # Ici la fonction add_10() prend en argument la # une variable et retourne sa valeur augmentée de 10 # la valeur retournée est explicitement affectée # à la variable globale *a* >>> a = add_10(a)
Le nommage de la fonction permet de rendre explicite son traitement: on l' appelle *add_10*. On réaffecte explicitement le retour de add_10 à la variable globale *a*. On lit de manière très explicite que *a* va valoir le résultat de *add_10 sur *a*. On voit que cette notion de variable globale, cette notion d'instruction global est très trompeuse puisqu'elle rend toutes les modifications implicites, et qu'il vaut bien mieux travailler avec des retours de fonction pour contrôler de manière explicite le changement de nos variables globales.
Pour expliquer le comportement de l'instruction nonlocal, on va implémenter une petite fonction. nonlocal
sert à modifier la portée d'une variable locale pour accéder à une variable également locale mais dans une fonction englobante. Il faut donc définir des fonctions englobantes.
# définition de la variable globale a = 'a globale' def f(): # variable locale *a* dans f a = 'a de f' def g(): # l'instruction ci dessous indique que la variable # *a* n'est pas locale, c'est donc la variable # existante dans la fonction englobante f nonlocal a a = 'a de g' print(a) g() print(a) >>> f() a de g a de g >>> print(a) a globale
Dans *g*, on ajoute l'instruction nonlocal a
qui déclare que la variable *a* définit dans *g* n'est plus une variable locale de *g*, mais la variable locale qui est définie dans *f*. Donc depuis *g*, je peux directement modifier la variable locale de *f*. Lorsque on execute le *print(a)* depuis *g*, on voit apparaître 'a de g' ; mais dans *g*, on a redéfini la variable *a de f* avec la valeur la chaîne de caractères 'a de g', donc maintenant, lorsque je fais un *print(a)* dans *f* après avoir appelé *g*, on voit que *a* maintenant vaut bien 'a de g'. Elle a donc été modifiée depuis la fonction *g*. nonlocal permet donc de modifier les variables des fonctions englobantes.
Les instructions global et nonlocal permettent la modification de portée de variable. Cependant, gardez toujours en tête que les modifications implicites de variables représentent une source de confusion et une difficulté pour maintenir votre code. Il est préférable de réserver ces usages uniquement pour des applications avancées ne pouvant pas être traduites par des modifications explicites.
Python fournit un accès à la liste des noms et valeurs des variables visibles à cet endroit du code. Dans le jargon des langages de programmation on appelle ceci l'environnement. Cela est fait grâce aux fonctions built-in globals
et locals
.
globals
donne la liste des symboles définis au niveau de l'espace de noms du module.locals
donne les variables locales qui sont accessibles à cet endroit du code.