Espace de noms
Python stocke ses variable dans un objet appelé espace de noms. Une variable a un nom et est associé à un objet python. Il est important de dissocier le nom de l'objet pour une variable et surtout la variable de l'objet qu'elle représente.
Variable et objet
La quasi-entièreté des langages actuellement sont dit objet. C'est à dire que :
- ce que manipule un programme est appelé objet.
- les variables représentent les objets. On dit aussi parfois qu'une variable est une référence d'un objet.
Ces langages permettent de créer des programmes en utilisant uniquement les deux mécanismes ci-dessous :
Pour qu'un programme objet fonctionne, on a besoin de deux mécanismes :
- un moyen de stocker des données et de les manipuler (les objets et leurs méthodes)
- un moyen d'y accéder (les variables)
Objets
On y reviendra, mais pour l'instant considérez qu'un objet est une structure de donnée générique permettant de gérer tout ce dont à besoin un programme :
- des données
- des fonctions
- des modules
- ...
Tout est objet dans un langage objet.
Variables
Les variables sont des références aux objets. Pour ce faire, on utilise l’opérateur d’affectation =
:
variable = objet
A gauche de l’opérateur =
se trouve une variable (en gros, quelque chose ne pouvant commencer par un nombre) et à droite un objet. Dans toute la suite du programme, dès que le programme rencontrera le nom, il le remplacera par l'objet.
Un variable n'est PAS une chaîne de caractères. Une chaîne de caractères est un objet alors qu’une variable est un alias vers un objet.
Il est important de comprendre que l’opérateur d’affectation =
n’est pas symétrique. À gauche, des variables et à droite, des objets.
Une variable n'est pas l'objet, c'est une référence à celui-ci
La variable peut être vue comme un nom de l'objet à ce moment du programme. Un objet pourra avoir plein de noms différents au cours de l'exécution du programme, voire plusieurs noms en même temps.
Pour s'y retrouver et avoir une procédure déterministe pour retrouver les objets associés aux variables, voire choisir parmi plusieurs variables de même nom, elles sont regroupées par ensembles — nommés espaces de noms — hiérarchiquement ordonnés.
Espaces de noms
Les espaces de noms sont des objets de python qui nous permettent de lier variables et objets :
- on considère que les objets sont stockés dans l'espace des objets : cet espace est unique
- on accède aux objets via leurs noms, eux même stockés dans des espaces de noms qui sont des objets comme les autres : il y a de nombreux espaces de noms.
Pour chaque espace de noms :
- il ne peut y avoir 2 noms identiques dans un même espace de noms
- à chaque nom est associé un objet
- certains espaces de noms possèdent un parent
Pour expliciter comment tout ça se passe, on va se concentrer sur le langage python, mais la procédure est similaire pour les autres langages à objets.
Lorsque l'on exécute un programme, un premier espace de noms est créé :
Au démarrage d'une exécution d'un programme, l'espace de noms principal, nommé global
est créé. C'est à partir de lui que toutes les variables doivent être atteintes.
Au départ, il ne contient rien, à part des noms commençant et finissant par __
, qui sont utilisés par python.
Pour voir les noms définit dans l'espace de noms global, on utilise en python la fonction globals()
qui rend un dictionnaire contenant le nom et l'objet associé à chaque variable.
Au démarrage del'interpréteur, il n'y pas grand chose dans globals :
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
A tout moment de l'exécution d'un programme, un espace de noms pourra être créé. En revanche :
A tout moment du programme, on pourra créer un nouvel espace de noms : de nombreux espaces de noms pourront être définis, mais il existera toujours un espace de noms courant où l'on créera les variables et où on cherchera les noms par défaut.
On donnera dans la suite de cette partie des exemples qui permettront de mieux comprendre ce processus.
Pour voir les noms définis dans l'espace de noms courant, on utilise en python la fonction locals()
.
Noms et variables
Prenons plusieurs exemples, qui illustreront les cas principaux.
Association objet et noms
Considérons le programme suivant :
x = 1
y = 1
Exécutons le ligne à ligne :
- avant l'exécution de la première ligne :
- on a un unique espace de noms (
global
) qui est l'espace courant (en vert sur la figure)
- on a un unique espace de noms (
- on exécute la première ligne. Elle s'exécute ainsi :
- on commence à droite du
=
: on crée un objet de type entier - on crée le nom
x
dans l'espace de noms courant (iciglobal
) et on lui affecte l'objet.
- on commence à droite du
- on exécute la deuxième ligne. Elle s'exécute ainsi :
- on commence à droite du
=
: on crée un objet de type entier - on crée le nom
y
dans l'espace de noms courant (iciglobal
) et on lui affecte l'objet.
- on commence à droite du
A la fin du programme, il y a 2 objets entiers différents (même si tous les 2 valent 1), dont les noms sont, dans l'espace de noms global, respectivement x
et y
.
Réutilisation du même nom
x = 1
x = 3
Exécutons le ligne à ligne :
- avant l'exécution de la première ligne :
- on a un unique espace de noms (
global
) qui est l'espace courant (en vert sur la figure)
- on a un unique espace de noms (
- on exécute la première ligne. Elle s'exécute ainsi :
- on commence à droite du
=
: on crée un objet de type entier - on crée le nom
x
dans l'espace de noms courant (iciglobal
) et on lui affecte l'objet.
- on commence à droite du
- on exécute la deuxième ligne. Elle s'exécute ainsi :
- on commence à droite du
=
: on crée un objet de type entier - on crée le nom
x
dans l'espace de noms courant (iciglobal
) et on lui affecte l'objet.
- on commence à droite du
Notez que le fait qu'un nom identique existe déjà n'est pas important. Le nouveau nom écrase l'autre :
Dans un espace de noms, chaque nom est différent. Réutiliser le même nom remplace le nom précédent.
Le programme a créé 2 objets (un entier valant 1 et un entier valant 3), mais à la fin de la deuxième ligne du programme, seul l'entier valant 3 a un nom (x
).
Comme il est maintenant impossible d'accéder à l'entier valant 1
: python le détruit.
Tout objet qui n'est plus référencé par une variable est détruit par un mécanisme appelé ramasse-miettes.
Un objet peut avoir plusieurs noms
x = 1
y = x
Exécutons le ligne à ligne :
- avant l'exécution de la première ligne :
- on a un unique espace de noms (
global
) qui est l'espace courant (en vert sur la figure)
- on a un unique espace de noms (
- on exécute la première ligne. Elle s'exécute ainsi :
- on commence à droite du
=
: on crée un objet de type entier - on crée le nom
x
dans l'espace de noms courant (iciglobal
) et on lui affecte l'objet.
- on commence à droite du
- on exécute la deuxième ligne. Elle s'exécute ainsi :
- on commence à droite du
=
: on cherche le nomx
dans l'espace de noms courant. On le trouve et on lui substitue son objet (un entier valant 1) - on crée le nom
x
dans l'espace de noms courant (iciglobal
) et on lui affecte l'objet.
- on commence à droite du
Le programme n'a crée qu'un objet (un entier valant 1) et il a deux noms (x
et y
) :
Dans un même espace de noms, un même objet peut être référencé plusieurs fois, sous plusieurs noms différents.
Les noms ne sont jamais utilisés en tant que tel. Dès qu'ils sont rencontrés, ils sont immédiatement remplacés par les objets qu'ils référencent.
Pour exécuter une instruction, on commence toujours par remplacer les variables par les objets qu'elles référencent.
La remarque précédente permet de comprendre mieux ce que fait le code suivant (et pourquoi cela fonctionne) :
x = 1
y = 3
x, y = y, x
solution
solution
Il échange les objets référencés par x
et y
.
Cela marche car on commence par remplacer les variables par les objets (la droite du =
) avant de créer les variables (la gauche du =
).
Port d'attache d'un espace de noms
Les espaces de noms sont des objets spéciaux qui ne peuvent vivre indépendamment. Il sont toujours rattachés à leur contexte qui est soit :
- le programme principal : c'est le cas de l'espace de nom globals
- une fonction : crée lors de l'appel d'une fonction pour gérer ses paramètre et variables locales.
- un module : les espaces de noms crées après un import
Fonctions
L'exécution d'une fonction est un moment où un espace de noms est créé. Cela se passe selon le processus suivant :
Lorsque l'on exécute une fonction on procède comme suit :
- on crée un nouvel espace de noms $F$
- l'espace de noms courant est affecté au parent de $F$
- $F$ devient le nouvel espace de noms courant.
- on affecte les paramètres de la fonction à leurs noms
- on exécute ligne à ligne la fonction
- le parent de $F$ devient le nouvel espace de noms courant
- on supprime l'espace de noms $F$
Exécution d'une fonction
def f(x):
i = 2 * x
return i + 3
i = 2
x = f(i)
Exécutons le ligne à ligne :
- avant l'exécution de la première ligne :
- on a un unique espace de noms (
global
) qui est l'espace courant (en vert sur la figure)
- on a un unique espace de noms (
- la ligne 2 définit une fonction de nom
f
qui est ajouté à l'espace de noms courant. - on passe directement à la ligne 5 puisque les lignes 3 et 4 sont le contenu de la fonction.
- Cette ligne crée un objet entier (valant 2) et l'affecte au nom
i
.
- Cette ligne crée un objet entier (valant 2) et l'affecte au nom
- la ligne 6 est encore une affectation. On commence par trouver l'objet à droite du
=
c'est le résultat def(i)
. Il faut donc exécuter la fonctionf
pour connaître cet objet :- on cherche l'objet associé à
i
qui sera le (premier) paramètre de la fonction - on crée un espace de noms qui devient l'espace de noms courant :
- l'ancien espace de noms courant devient son parent
- on affecte le premier paramètre de
f
au nomx
(le nom du premier paramètre def
lors de sa définition)- les nouveaux noms sont toujours créés dans l'espace de noms courant
- on exécute la ligne 2 qui est la première ligne de la fonction
f
:- on crée un objet entier (valant 4) qui est le résultat de l'opération à droite du
=
(notez que le nomx
est bien défini dans l'espace de noms courant) et on l'affecte au nomi
dans l'espace de noms courant
- on crée un objet entier (valant 4) qui est le résultat de l'opération à droite du
- on exécute la ligne 3 :
- on crée l'objet résultant de l'opération somme (un entier valant 7)
- la fonction est terminée, son espace de noms courant est détruit
- l'espace de noms courant devient le parent de l'espace de noms détruit
- on rend l'objet résultat de la fonction
- la droite du signe
=
de la ligne 6 est trouvée (c'est un entier valant 7) et il est affecté à la variablex
de l'espace de noms courant (qui est à nouveauglobal
)- les objets sans nom sont détruits
- on cherche l'objet associé à
Espaces de noms parent
L'espace de noms parent sert lorsque l'on cherche un nom qui n'est pas défini dans l'espace de noms courant :
Si un nom est recherché, mais que celui-ci n'est défini dans l'espace de noms courant, le nom est recherché dans l'espace de noms parent de l'espace courant.
def f(x):
i = C * x
return i + 3
C = 2
i = 2
x = f(i)
Lors de l'exécution de la fonction f
(instruction de la ligne 7), sa première ligne cherche la variable nommée C
. On se trouve dans cet état là :
La variable C
n'existe pas dans l'espace de noms courant (celui de f
), le programme va alors chercher dans l'espace de noms parent s'il existe. Ici c'est le cas puisque l'espace parent de f
est global
dans lequel C
est défini : le programme ne produit donc pas une erreur et trouve le bon objet.
Les variables sont toujours créées dans l'espace de noms courant, mais leur recherche remonte de parent en parent jusqu'à la trouver.
Import de module
Lorsque l'on importe un fichier, un espace de noms est créé et le fichier entier est lu. Lors de sa lecture, les noms définis sont placés dans cet espace.
Les modules possèdent un espace de noms qui contient les variables qui y sont définies
import random
from math import log
print(log(random.randrange(1, 43)))
Avant l'exécution de l'instruction print
on est dans cet état :
On accède à l'espace de noms du module par la notation pointée : random.randrange
signifie le nom randrange
dans l'espace de noms de random
.
Notez que le module math
n'a plus d'espace de noms associé puisque l'on a juste récupéré un nom qui y est défini.
Notation pointée
En python, (pratiquement) tout a un espace de nom. On s'en sert dès qu'on utilise la notation pointée.
on l'a vue pour les modules, mais c'est aussi vrai pour les objets. En considérant le code suivant :
c = "coucou"
c2 = c.uppercase()
Le nom uppercase
est défini dans l'espace de noms de la chaîne de caractères "coucou"
(en fait, c'est dans sa classe, mais on le verra précisément plus tard).
C'est une notation très puissante ! Il ne faut pas avoir peur de chaîner ces notations. On appelle cela des chaînages :
a.b.c.d()
Signifie :
- On exécute
d
qui est dans l'espace de noms dea.b.c
c
est dans l'espace de noms dea.b
b
est dans l'espace de noms dea
a
est dans l'espace de noms courant