Python Espace des noms

Identification

Infoforall

21 - Espaces des noms


Non finalisée. Activité en cours de transformation

Beaucoup de paragraphes répartis dans d'autres activités.

Nous allons en voir (encore) un peu plus sur les variables dans Python. Pourquoi ? Pour éviter de devoir être Sauron, l'Oeil qui voit tout pour pouvoir programmer.

Sauron voit tout
L'Oeil de Sauron : Gif provenant de https://gifer.com

Vous allez voir aujourd'hui pourquoi il est inutile de donner des noms différents aux paramètres de plusieurs fonctions. Sur l'exemple ci-dessous, Python ne fera aucune confusion entre les trois versions du paramètre feutre :

1 2 3
def deplacer(feutre, x, y) : def trois(feutre, distance, angle) : def triangle(feutre, cote) :

Heureusement car sinon, il faudrait à chaque fois donner un nom un peu différent comme sur cet exemple :

1 2 3
def deplacer(feutre, x, y) : def trois(ftr, distance, angle) : def triangle(f, cote) :

Vous imaginez si on devait se souvenir des noms de toutes les variables et paramètres utilisés ailleurs dans le programme de 3 millions de lignes repartis dans 300 fichiers différents... Il n'y aurait qu'un seul informaticien au monde et il habiterait au Mordor...

Tour de Sauron
Bienvenue au Mordor ! Image CC Public Domain Certification

En résumé, cette image ne permet pas de tout expliquer et risque même de créer des programmes totalement disfonctionnels :

calcul bizarre
Image issue de http://hcc-cs.weebly.com/variables.html

Cette image n'était qu'une première approche. Nous allons en apprendre plus sur la façon dont Python gère ces mystérieuses boîtes.

Logiciel nécessaire pour l'activité : Thonny

Evaluation ✎ :

Documents de cours : open document ou pdf

Résumé : Version HTML ou fond blanc ou ou PDF (couleur ou gris)

1 - Cours : l'espace des noms

Nous avons vu que la gestion des variables peut être vue comme une simple affaire de boîtes sur les cas simples.

représentation en boîte

Mais cette version simple ne suffit pas pour tout expliquer.

Comprendre comment cela fonctionne réellement va nous permettre de comprendre ce que sont réellement les données muables et les données immuables.

1.1 Variable : espace des noms

Le principe

Une variable Python est en réalité un NOM permettant d'atteindre une ZONE-MEMOIRE.

Imaginons qu'on tape ceci dans la console :

>>> a = 5 >>> b = 9 >>> a 5

Lorsque l'interpréteur Python tombe sur a, il va lire un tableau nommé espace des noms qui lui permet de savoir quelle zone mémoire il doit aller lire.

espace des noms

Ci-dessus, on voit que le NOM a mène à la ZONE-MEMOIRE identifiée 68, qui contient 5.

De la même façon, le NOM b mène à la ZONE-MEMOIRE identifiée 85 qui contient 9.

L'intérêt

L'espace des noms est donc une interface entre l'utilisateur humain et la mémoire. Ce mécanisme permet :

  • de programmer sans se préoccuper des détails du stockage réel
  • de limiter les erreurs de manipulation en mémoire puisque c'est Python qui se charge de gérer les zones.
Le mécanisme réel d'affectation

Lorsqu'on tape a = 2 + 3, voici ce que fait Python discrétement :

  1. Il évalue à 5 l'expression 2+3
  2. Il cherche une zone-mémoire pour stocker votre 5 (la zone 68 sur l'exemple)
  3. Il rajoute l'association entre a et la zone-mémoire dans l'espace des noms.
espace des noms
1.2 Variable : fonction native id()

La fonction native id() renvoie l'identifiant de la zone-mémoire qui est associée à une variable dans l'espace des noms.

espace des noms type simple
>>> a = 10 >>> id(a) __108 >>> id(10) __108

Votre propre valeur peut être différente bien entendu.

CPython

Thonny utilise une implémentation de Python réalisée en C. La fonction id() renvoie alors réellement l'adresse-mémoire.

1.3 Variable : modification d'une variable

Lorsqu'on réalise une nouvelle affectation sur une variable préexistante, on change simplement l'identifiant associé à la variable dans l'espace des noms.

Imaginons ceci qui correspond à l'exemple ci-dessus :

>>> a = 5 # initialement a est associée à l'id 68 >>> a = 50 # ici, a est associée à l'id 99

Il faut donc comprendre qu'à chaque fois qu'on réalise une affectation, on modifie l'association NOM DE VARIABLE - ZONE-MEMOIRE.

1.4 Variable : les alias

Lorsqu'on note b = a, on ne duplique pas le contenu de a dans a.

En tapant b = a, on demande à ce que b référence la même zone-mémoire que a.

On dit que b et a sont des alias : cette zone-mémoire porte deux noms différents.

>>> a = 50 # a référence la zone 99 >>> id(a) 99 >>> b = a # b référence la zone de a, donc 99 aussi >>> id(b) 99

Par contre, si on modifie l'une des deux variables, elles feront référence à deux zone-mémoires différentes ensuite.

>>> a = 5 # a référence maintenant la zone 68 >>> id(a) 68 >>> id(b) # b référence toujours la zone 99 99
1.5 Variable : mécanisme pour les types construits

Principe et analogie

Une variable référençant un type simple pointe directement vers la zone-mémoire du contenu.

Une variable référençant un type construit pointe vers la zone-mémoire du CONTENEUR.
A partir de cette zone CONTENEUR, on pourra atteindre le CONTENU voulu.

C'est exactement comme lorsque vous voulez localiser un livre dans une bibliothèque : vous commencez par localiser la bonne étagère (le CONTENEUR) puis vous fouillez l'étagère pour trouver le livre (le bon CONTENU).

Exemple avec un tableau contenant 20, 100, 15

>>> t = [20, 100, 15] >>> id(t) # ici, on demande l'adresse du conteneur __5752 >>> id(t[0]) # ici, on demande l'adresse du contenu 0 __0448 >>> id(t[1]) # ici, on demande l'adresse du contenu 1 __3008 >>> id(t[2]) # ici, on demande l'adresse du contenu 2 __0288

Voici le schéma de principe (on expliquera dans la partie "données" pourquoi les adresses d'un système 64 bits nécessitent 8 octets pour être codées).

Lorsqu'on tape t[2], voici ce que fait Python :

  • Il localise le tableau-CONTENEUR à l'adresse 5752.
  • On cherche l'indice 2, on rajoute deux fois 8 octets à cette adresse et on obtient l'adresse (5752 + 16) = 5768.
  • En 5768, on apprend que le CONTENU se situe à l'adresse 0288.
  • Python se rend en 0288 et trouve l'entier 15.

Les autres types construits

Il s'agit du même type de mécanisme pour tous les types construits de Python.

La variable-tuple permet de localiser l'adresse du CONTENEUR.
A l'aide de l'indice, on peut localiser l'adresse d'un CONTENU précis.

La variable-string permet de localiser l'adresse du CONTENEUR.
A l'aide de l'indice, on peut localiser l'adresse d'un CONTENU précis.

La variable-dictionnaire permet de localiser l'adresse du CONTENEUR.
A l'aide de la clé, on peut localiser l'adresse d'un CONTENU précis.

1.6 Variable : structure immuable / non mutable

Principe

Une structure immuable est une structure dans laquelle il n'est pas possible de modifier l'adressage des CONTENUS après création du CONTENEUR.

Sur l'exemple ci-dessous, cela veut dire qu'on ne peut pas modifier les cases jaunes pâles des zones associées à notre structure. Les flèches rouges sont donc définitives.

La seule façon de "modifier" une telle variable est de faire une nouvelle affectation et donc de changer l'adresse de la structure.

En Python : tuple et str.

Exemple : une variable a contenant le tuple (20, 100, 15) puis le tuple (20, 20, 20)

>>> tu = (20, 100, 15) # affectation d'un tuple-CONTENEUR >>> id(tu) # adresse du conteneur __5752 >>> tu = (20, 20, 20) # Affectation d'un tuple-CONTENEUR de même nom >>> id(tu) # adresse du conteneur, qui a changé ! __6000

En images :

1.7 Variable : structure muable / mutable

Principe

Une structure muable est une structure dans laquelle il est possible de modifier l'adressage des CONTENUS après création du CONTENEUR.

Sur nos exemples, cela veut dire qu'on peut modifier les cases jaunes pâles.

On peut donc modifier le CONTENU d'un CONTENEUR sans modifier son adresse propre.

Pour cela, on effectue une affectation non pas sur le CONTENEUR mais sur un CONTENU.

Exemple : une variable a contenant le tableau de contenu [20, 100, 15] devenant [20, 20, 20]

>>> t = [20, 100, 15] # affectation d'un tableau-CONTENEUR >>> id(t) # adresse du conteneur __5752 >>> t[1] = 20 # affectation du CONTENU 1 >>> t[2] = 20 # affectation du CONTENU 2 >>> t [20, 20, 20] >>> id(t) # adresse inchagée ! __5752

En images :

1.8 Site Web Python Tutor

Python Tutor est un site Web permettant d'observer la séquence d'exécution. Il affiche les zones-mémoires sans leur attribuer d'identifiants ou adresses car leur gestion interne réelle dépend de votre implémentation de Python : CPython, IPython...

Pour l'utiliser, il suffit :

  • de partir sur Python Tutor (https://pythontutor.com/)
  • de cliquer sur le lien noté Visualize your code and get live help now.
  • de rentrer le code fourni ci-dessous.
  • de cliquer sur Visualize Execution
  • cliquer sur Next (en avant, vers l'avant...) vous permettra alors d'exécuter la ligne actuellement surlignée, et de passer à la ligne suivante à exécuter.
Avec un type simple
CLIQUER SUR L'IMAGE pour ANIMER ou STOPPER

2 -

3 - Cours : les espaces des noms

3.1 Variables globales

3.1.1 Variable globale

On appelle variable globale une variable permanente qui est définie dans le corps du programme, hors de toute fonction.

Permanente, elle existe jusqu'à la fin du programme.

  • Depuis le programme principal : elle est accessible en lecture et modification
  • Depuis les fonctions : elle est accessible en lecture uniquement : une nouvelle affectation avec ce nom crée simplement une variable locale de même nom.

Seule possibilité pour qu'elle disparaisse : la supprimer volontairement avec le mot-clé del. Exemple : del a permet de supprimer la variable a.

portée des variables globales
Qui peut utiliser la variable globale x ?
3.1.2 Exemple : lecture possible

En ligne 2, on lit directement valeur globale depuis la fonction.

1 2 3 4 5 6 7 8 9
def f(): a = valeur * 2 print("Depuis la fonction :") print(a) # on affiche la variable locale valeur = 10 f() valeur = 30 f()
Depuis la fonction : 20 Depuis la fonction : 60
Distinction Lecture globale possible
CLIQUER SUR L'IMAGE pour ANIMER ou STOPPER
3.1.3 Exemple : modification impossible

Voici un autre programme montrant que la fonction ne parvient pas à modifier la variable globale : ligne 2, on crée en réalité une variable locale qui disparaitra après l'appel.

Il y aura donc temporairement deux variables portant le même nom. Depuis la fonction, on lira juste la variable locale, et on ne pourra plus voir la variable globale.

1 2 3 4 5 6 7 8 9 10 11
def f(): valeur = 100 # on tente de modifier valeur globale... print("Pendant l'appel à f() :") print(valeur) # on tente d'afficher pendant l'appel valeur modifiée valeur = 2 print("Avant l'appel à f() :") print(valeur) # on tente d'afficher valeur avant l'appel f() print("Après l'appel à f() :") print(valeur) # on tente d'afficher valeur après l'appel
Avant l'appel à f() : 2 Pendant l'appel à f() : 100 Après l'appel à f() : 2
Modification globale impossible depuis une fonction
CLIQUER SUR L'IMAGE pour ANIMER ou STOPPER
portée des variables globales
Affectation en local avec le même nom qu'en global : pas de modification au global

Il faut bien comprendre que faire une affectation en local en utilisant le même nom qu'une variable globale ne fait que créer temporairement une variable locale qui va cacher la variable globale.

Une fois sortie de la fonction, la variable globale n'aura pas bougé.

3.1 Variables locales

3.1.1 Variable locale

On appelle variable locale une variable temporaire qui est définie dans une fonction.

  1. Avant l'appel, cette variable n'existe pas.
  2. Pendant l'appel, elle existe le temps de cet appel précis.
  3. Après le return, la variable est détruite définitivement.

Conséquences :

  • Depuis sa propre fonction : la variable est accessible en lecture et modification.
  • Depuis le programme principal : elle est inconnue.
  • Depuis une autre fonction : elle est inconnue.
  • Depuis un autre appel de la même fonction : la variable du nouvel appel porte le même nom, mais il s'agit bien d'une autre variable.
portée des variables locales
Qui peut utiliser la variable locale x ?
3.1.2 Exemples d'illustration
1 2 3 4 5 6 7 8 9
def f(valeur): a = valeur * 2 print("Depuis la fonction :") print(a) # on tente d'afficher a pendant l'appel print(a) # on tente d'afficher a avant l'appel f(10) print(a) # on tente d'afficher a après l'appel f(30)

Ligne 6 (avant l'appel)  lire a depuis le programme principal ne fonctionne pas. On provoquerait une erreur puisque a n'existe pas.

Ligne 8 (après l'appel f(10)) : lire a depuis le programme principal provoque une erreur puisque a n'existe plus.

print(a) # on affiche la variable locale NameError: name 'a' is not defined

Ligne 4 (pendant l'un des appels) : lire a depuis la fonction fonctionnerait (mais il faut supprimer ou commenter les lignes 6 et 8 avec # pour les empêcher de s'exécuter).

Depuis la fonction : 20 Depuis la fonction : 60
Distinction Locale Globale
CLIQUER SUR L'IMAGE pour ANIMER ou STOPPER
portée des variables locales
3.2 Variables en Python : l'espace des noms

Lors d'une affectation, Python crée une association entre le nom de la variable et un contenu en mémoire.

une sorte de liaison

L'espace des noms correspond au mécanisme qui lie le nom de la variable à un contenu mémoire, représenté ici par le simple trait entre les deux.

3.2.1 L'espace des noms de Python

Les associations entre nom et contenu sont mémorisées dans une table qu'on nomme l'espace des noms.

Imaginons qu'on dispose de ce programme :

1 2 3
a = 10 b = 20 c = b - a

Voici l'image simpliste qu'on peut se faire de la liaison entre l'espace des noms et l'espace mémoire :

liaison magique espace des noms et mémoire

Cette liaison est réalisée en associant dans une table chaque nom de variable à une zone mémoire.

espace des noms type simple

En Python, on peut récupérer l'adresse / référence / identifiant à l'aide de la fonction native id().

>>> a = 10 >>> b = 20 >>> id(a) 108 >>> id(b) 524
3.2.2 LES espaceS des noms de Python

Il existe en réalité plusieurs espaces des noms pouvant référencer l'espace mémoire :

  • L'espace des noms global et permanent du programme : il existe pendant tout le déroulement du programme.
  • Chaque appel de fonction crée un espace des noms local temporaire qui est détruit après l'appel.

On peut voir visuellement ces zones dans Python Tutor. La zone grise est l'espace des noms actuellement utilisé par Python.

Exemple avec ce court programme :

1 2 3 4 5
def f(x): return x * 2 a = 10 b = f(a)
espaces des noms
3.2.3 Utilisation par Python

Lorsque Python doit évaluer une variable dans une fonction :

  1. Il commence par chercher dans l'espace local.
  2. Si il ne trouve pas, il cherche ensuite dans l'espace global.
  3. S'il ne trouve toujours pas de variable portant ce nom, il déclenche une exception NameError.

Traduit en Python, cela donnerait quelque chose comme ceci :

1 2 3 4 5 6 7 8 9 10 11 12 13
def evaluer_variable(nom): espace_local = locals() # espace local actuel sous forme d'un dico espace_global = globals() # idem mais pour l'espace global if nom in espace_local: # si le nom est bien une clé de l'espace local return espace_local[nom] # on renvoie son contenu mémoire elif nom in espace_global: # sinon si le nom est une clé de l'espace global return espace_global[nom] # on renvoie son contenu mémoire else: # sinon, raise NameError # on lève l'exception NameError
ATTENTION

Dans d'autres langages (comme le C par exemple), l'adresse d'une variable ne change pas après déclaration. C'est bien le contenu de la case mémoire qui change.

14° Utiliser d'abord Python Tutor avec ce programme qui utilise des tuples. Répondre ensuite aux questions.

1 2 3 4 5 6 7 8
tu = (10, 20, 30) print(id(tu)) tu2 = tu print(id(tu2)) tu = (1, 1, 1) print(id(tu))

Questions

Associer l'une des phrases ci-dessous à chacune des lignes 1, 4 et 7 du programme.

  1. On écrase une variable existante en lui associant un nouveau CONTENEUR-tuple
  2. On crée une variable en lui associant un nouveau CONTENEUR-tuple
  3. On crée un alias vers un CONTENEUR-tuple

...CORRECTION...

L1 : On crée une variable en lui associant un nouveau CONTENEUR-tuple

L4 : On crée un alias vers un CONTENEUR-tuple

L7 : On écrase une variable existante en lui associant un nouveau CONTENEUR-tuple

15° Utiliser d'abord Python Tutor avec ce programme qui utilise des tableaux. Répondre ensuite aux questions.

1 2 3 4 5 6 7 8
t = [10, 20, 30] print(id(t)) t[1] = 10 print(id(t)) t[2] = 10 print(id(t))

Questions

  1. Pourquoi peut-on dire qu'il s'agit du même tableau tout au long du programme ?
  2. Quel est son contenu au début du programme ?
  3. Quel est son contenu final ?
  4. Lors de l'utilisation du signe = aux lignes 4 et 7, l'affectation se fait-elle sur le tableau ou sur un contenu du tableau ?

...CORRECTION...

  1. L'identifiant reste le même du début à la fin.
  2. Au début, t référence [10, 20, 30]
  3. A la fin, t référence [10, 10, 10]
  4. Les affectations des lignes 4 et 7 se font sur un contenu t[i] du tableau.

4 - FAQ

On peut détruire une variable ?

Oui, on peut libérer la place mémoire attribuée à une variable. Pour cela, il faut utiliser le mot-clé del.

Exemple :

>>> a = 5 >>> a 5 >>> del a >>> a NameError: name 'a' is not defined

J'ai vu une notation bizarre : a += 1

Effectivement, on peut également utiliser une autre notation.

1 2
a = 10 a += 1

Cela donne ici le même résultat que ceci :

1 2
a = 10 a = a + 1

Attention, les deux façons de faire sont équivalentes ici, mais pas toujours. Evitez ces notations pour l'instant. De toutes manières, elles ne seront pas utilisées dans les sujets de NSI. Gardez la méthode n°2. C'est plus long mais c'est moins compliqué à comprendre de toutes manières.

5 -

6 -

Reste encore un problème.

Si j'ai 1000 données à stocker, je vais avoir besoin de 1000 variables. Pas cool. La solution dans l'activité suivante.

Activité publiée le 08 09 2023
Dernière modification : 13 09 2023
Auteur : ows. h.