Lorsque nous avons parlé de la notion de portée de variable, nous avons expliqué que nous pouvions avoir une variable d'un nom donné, par exemple une variable *x*, qui coexiste dans le même fichier à l'intérieur d'une fonction et à l'intérieur d'un module. Nous avons également expliqué que les modules isolaient complètement leurs variables.
Ce mécanisme d'isolation des variables fonctionne avec ce qu'on appelle des espaces de nommage.
Un espace de nommage regroupe un ensemble de variables appartenant à un objet. En Python, les modules, les fonctions et nous verrons également les classes et les instances, définissent des espaces de nommage. Dans des langages comme le C, où vous n'avez pas cette notion d'espace de nommage, il faut faire extrêmement attention de ne pas définir des variables qui se surchargent l'une l'autre, qui s'écrasent l'une l'autre. Et en Java, qui est un langage orienté objet, qui a également une notion d'espace de nommage, vous devez créer des classes à chaque fois que vous voulez isoler les espaces de nommage. En Python, comme les modules définissent des espaces de nommage, vous avez quasiment gratuitement cette notion d'isolation des variables dès que vous commencez à écrire votre première ligne de code. Nous allons voir dans la suite comment est-ce que les modules isolent les variables à travers les espaces de nommage.
Regardons cette notion d'isolation des variables par les espaces de nommage dans les modules. On peut commencer par créer un module `spam.py`, donc un fichier python qui s'appelle `spam.py` contenant une constante *x* et une fonction f qui l'affiche:
""" Le module spam contient une variable et une fonction""" x = 1 def f(): print(x)
On définit un deuxième fichier python, `egg.py`, comme ceci:
import spam x = 2 def f(): print(x) f() spam.f() print(spam.x)
On va exécuter le le programme python à partir de egg.py
, depuis la ligne de commande:
$ python3 egg.py 2 1 1
Analysons les valeurs retournées par le programme. L'espace des objets est la zone mémoire de l'ordinateur qui va contenir tous les objets créés. Python gère également les espaces de nommage.
Ici, on se focalise sur l'espace de nommage des modules ( on ne détaillera pas les espaces des fonctions). On va avoir un espace de nommage pour *spam* et un espace de nommage pour *egg*. Je vous rappelle cependant que les fonctions ont leur propre espace de nommage qui est créé à l'appel de la fonction et détruit dès que la fonction retourne.
Maintenant, commençons par la première ligne de code executée lors de l'appel du script egg.py
;
L'interpréteur va évaluer chaque ligne de code. La première ligne de code, lorsqu' on appelle `python3 egg.py`, consiste à évaluer import spam
. L'interpréteur recherche fichier spam.py, nous verrons dans une prochainement le mécanisme exact de recherche de ce fichier, puis va créer l'objet *spam* (l'objet module *spam*). Pour cela il va commencer à évaluer le code du fichier spam.py
.
La première ligne de code, dans spam.py
, est x = 1
.
Puis arrive la définitionde la fontion f().
Maintenant que le code de *spam.py* est évalué, on retourne dans le code de *egg.py*, donc dans le code du module *egg* ;
L'interpréteur a terminé l'importation du module *spam*, il crée l'objet module et crée une variable *spam* dans l'espace de nommage de *egg* qui va référencer cet objet module. Évidemment, l'objet module va lui-même référencer l'objet fonction et l'entier.
Pour l'instruction suivante x = 2
; l'interpréteur crée l'entier 2 puis une variable *x* dans l'espace de nommage de *egg* qui référence 2. Puis il définit la fonction *f* dans le module *egg*, donc l'objet fonction et une variable *f* qui référence cet objet fonction.
Les instructions suivantes sont des appels de fonctions. L'appel de *f*. *f* est une variable; on peut la chercher avec la règle LEGB:
Maintenant, l'appel de spam.f()
. La notation “point” veut dire qu' on accèder à *f* dans l'espace de nommage de *spam*. On appelle la fonction *f* dans l'espace de nommage de *spam* ; La fonction *f* fait un print de *x* ; *x* est une variable dans *spam* ; on cherche *x* avec la règle LEGB ; *x* est définie localement: non ; il n'y a pas de fonction englobante ; elle est définie globalement, c'est la variable définie dans l'espace de nommage de *spam*. Que vaut cette variable au moment de l'évaluation de *f* ? On regarde, *x* dans l'espace de nommage de *spam* vaut actuellement 1 ; ça va donc afficher l'entier 1.
Puis pour finir, on appelle print(spam.x)
, On accède à l'attribut *x* qui est défini dans l'espace de nommage de *spam*. Que vaut *x* à ce moment-là dans l'espace de nommage de *spam* ? Il vaut toujours 1, donc `print(spam.x)` va donc afficher l'entier 1.
Cette notation objet.attribut est très importante. Elle permet d'accèder à l'attribut dans l'espace de nommage de l'objet. Lorsque cet objet est un module, ça veut dire: accède à l'attribut dans l'espace des variables globales du module. Nous verrons que, dans le cas des instances et des classes, nous avons un mécanisme de recherche d'attribut qui est un petit peu différent.
Les espaces de nommage en Python isolent les variables (compartimentent l’espace des variables), mais ils n'isolent pas les objets (tous instanciés dans le me espace objet). Par conséquent, avec des mécanismes d'espace de nommage, vous pouvez tout de même avoir des références partagées vers des objets mutables. Donc, vous avez deux variables dans deux espaces de nommage qui peuvent référencer le même objet. Cet objet étant mutable, si vous le modifiez, il sera modifié par effet de bord donc les deux variables dans les deux espaces de nommage différents verront cet objet modifié.
On peut également se demander comment est-ce que les espaces de nommage sont implémentés en Python. En Python, rien n'est caché donc on peut très facilement expliquer comment sont implémentés les espaces de nommage. En fait, ils sont implémentés en général sous forme de dictionnaires. Comment fonctionne cet espace de nommage ? La clé du dictionnaire correspond au nom de la variable ; la valeur correspondant à cette clé va être la référence vers l'objet qui est référencé par la variable. Donc lorsque vous ajoutez une variable dans un module, ça va créer une entrée dans l'espace de nommage de ce module, donc une clé correspondant au nom de la variable et une valeur qui est une référence vers l'objet associé à cette variable.
Les espaces de nommage permettent une parfaite isolation de vos variables, et leur grande force est que pour accéder à l'attribut dans un autre module, dans un autre objet, vous devez utiliser une notation explicite qui est votre objet point l'attribut. Donc en rendant ce mécanisme explicite, vous n'avez plus aucun risque d'erreur ou de collision de variables par erreur. Je voudrais également rajouter que la règle LEGB que nous avons vue permet en fait de savoir où la variable que vous référencez a été définie, c'est-à-dire dans quel espace de nommage cette variable a été définie. En fait, Python est vraiment construit autour de cette notion d'espace de nommage et cette notion d'espace de nommage rendant l'accès aux attributs explicites facilite extrêmement le développement et la maintenance du code.