Portée Var Globale

Identification

Infoforall

16 - Portée des variables globales


Nous avons vu dans l'activité précédente qu'il existe deux types de variables.

  1. les variables locales sont les variables déclarées dans une fonction.. Elles ne peuvent être lues et modifiées qu'à l'intérieur de leur fonction puisqu'elles n'existent que le temps de son exécution.
  2. portee-variable-locale
  3. les variables globales sont les variables qui sont créées directement dans le corps du programme.

Aujourd'hui, nous allons parler de ces variables globales.

Logiciel nécessaire pour l'activité : Python 3

Evaluation ✎ : - mais de la bonne pratique de programmation à connaître en ...évitant d'utiliser les variables globales si possible

Documents de cours : open document ou pdf

1 - Portée des variables globales

Variables globales

On nomme variables globales les variables déclarées en dehors des fonctions. Elles sont donc directement définies dans le programme principal.

Nous allons voir

  1. qu'on peut lire une variable globale depuis une fonction
  2. qu'on ne peut pas modifier une variable globale depuis une fonction.
portee-variable-globale

01° Regarder le programme ci-dessous. Donner :

  • le nom de la variable globale et
  • le nom de la variable locale.
1 2 3 4 5 6 7 8 9 10 11
# 1 - Déclaration des variables globales de départ mdp = 'Prépa MP2I pour les NSI' # 2 - Déclaration des fonctions def verifier(tentative): print(f"Dans la fonction : mdp a comme id {id(mdp)%1000}") return tentative == mdp # 3 - Programme principal print(f"Dans le corps du programme : mdp a comme id {id(mdp)%1000}")

...CORRECTION...

On voit clairement que la variable mdp est définie dans le corps du programme. C'est une variable globale qu'on pourrait nommer mentalement mdp_du_programme_-principal.

La variable tentative est le paramètre de la fonction. Il s'agit donc d'une variable locale qu'on pourrait nommer mentalement tentative_de_la_fonction_verifier.

02° Placer le programme en mémoire puis utiliser le programme en mode interactif comme proposé.

1 2 3 4 5 6 7 8 9 10 11
# 1 - Déclaration des variables globales de départ mdp = 'Prépa MP2I pour les NSI' # 2 - Déclaration des fonctions def verifier(tentative): print(f"Dans la fonction : mdp a comme id {id(mdp)%1000}") return tentative == mdp # 3 - Programme principal print(f"Dans le corps du programme : mdp a comme id {id(mdp)%1000}")

Voici des exemples d'appels à effectuer :

Dans le corps du programme : mdp a comme id 864 >>> verifier("123456") Dans la fonction : mdp a comme id 864 False >>> verifier("Prépa MP2I pour les NSI") Dans la fonction : mdp a comme id 864 True

Que fait la fonction ?

La fonction verifier() fait deux choses :

  1. Elle affiche l'identifiant de la variable mdp.
  2. Elle renvoie le booléen correspondant à l'égalité entre
    • Le contenu de la variable locale tentative
    • Le contenu de la variable globale mdp

Répondre aux questions suivantes :

  1. Observation : en comparant les identifiants lors des deux affichages, pourquoi peut-on affirmer qu'on lit bien la bonne variable ?
  2. Conclusion : parvient-on à lire la variable globale mdp depuis la fonction ?
  3. Exprimez en français la question qu'on pose à Python en ligne 8 avec tentative == mdp.

...CORRECTION...

Observation : On voit que la fonction parvient bien à lire le contenu de la variable globale définie dans le corps du programme lui-même. L'id reste le même : 864 sur mon exemple.

Conclusion : une fonction peut lire sans problème une variable globale.

tentative == mdp peut se traduire par "Est-ce que le code fourni par l'utilisateur correspond bien au mot de passe ?".

On voit donc que les fonctions peuvent lire les variables globales.

Mais peuvent-elles les modifier ?

03° Modifier le code de la fonction verifier() comme proposé (rajout de la ligne 7, en jaune).

1 2 3 4 5 6 7 8 9 10 11 12
# 1 - Déclaration des variables globales de départ mdp = 'Prépa MP2I pour les NSI' # 2 - Déclaration des fonctions def verifier(tentative): mdp = '1234' print(f"Dans la fonction : mdp a comme id {id(mdp)%1000}") return tentative == mdp # 3 - Programme principal print(f"Dans le corps du programme : mdp a comme id {id(mdp)%1000}")

Voici des exemples d'appels à effectuer :

Dans le corps du programme : mdp a comme id 424 >>> verifier("123456") Dans la fonction : mdp a comme id 656 False >>> verifier("Prépa MP2I pour les NSI") Dans la fonction : mdp a comme id 656 False >>> verifier("1234") Dans la fonction : mdp a comme id 656 True >>> mdp 'Prépa MP2I pour les NSI' >>> id(mdp) 424

Questions

Lancer le programme et les appels proposés puis répondre aux questions suivantes :

  1. Quel est l'id de mdp-du-programme-principal dans le corps du programme ?
  2. Quel est l'id de la variable mdp présente ligne 7 dans verifier() ? Devrait-on la nommer mdp-du-programme-principal ou mdp-de-verifier ?
  3. Lorsqu'on a utilisé mdp = '1234' dans la fonction, est-on parvenu à modifier mdp-du-programme-principal ?

...CORRECTION...

On pourrait croire que la variable mdp-du-programme-principal est modifiée dans la fonction. Mais non !

Il suffit de regarder les id pour voir ce qui se passe.

  1. Dans le programme principal : mdp-du-programme-principal a comme id 424.
  2. Dans la fonction verifier() : mdp a comme id 656 : il ne s'agit donc pas de la même variable que la précédente : on fait référence à une autre zone mémoire. On devrait donc la nommer mdp-de-verifier
  3. Lorsqu'on revient dans le programme, on affiche 'Prépa MP2I pour les NSI' puisqu'il s'agit de la variable mdp-du-programme-principal qui n'a pas été modifiée par la fonction.

04° La fonction verifier() a-t-elle réussi à modifier réellement la variable mdp-du-programme-principal ?

...CORRECTION...

Non, la ligne 7, mdp = '1234', ne permet que de créer une variable locale mdp-de-verifier portant le même nom que la variable globale mdp-du-programme-principal. Mais leurs ids sont différents : il s'agit donc bien de variables différentes.

05° La variable mdp-de-verifier existe-elle avant de faire appel à cette fonction ou après que la fonction ai répondu ?

...CORRECTION...

Non. Il s'agit d'une variable locale à la fonction. Elle est créé lorsqu'on rencontre la ligne 7 puis la référence est détruite lorsqu'on sort de la fonction.

06° Aller sur le site Python Tutor (http://pythontutor.com) et demander à voir l'exécution du programme suivant. Vous pourrez voir que la variable locale n-de-ajouter disparaît bien une fois qu'on sort de la fonction et qu'on parvient bien à lire la variable note-du-programme-principal depuis l'intérieur de la fonction.

1 2 3 4 5 6 7 8 9 10 11 12 13
# Variables globales bonus = 10 # Déclaration des fonctions def ajouter(): n = note + bonus return n # Corps du programme principal note = 8 b = ajouter() c = ajouter()

07° Lancer le programme sur Thonny ou Python Tutor.

1 2 3 4 5 6 7 8 9 10 11 12 13
# Variables globales bonus = 10 # Déclaration des fonctions def sympa(): note = 20 return note # Corps du programme principal note = 8 b = sympa() c = sympa()

Expliquer si la fonction est parvenue à modifier note-de-sympa ou note-du-programme-principal ?

...CORRECTION...

Ligne 11 : la variable note-du-programme-principal est affectée à 8.

Ligne 12 : appel à sympa().

Ligne 07 : la variable note-de-sympa est affectée à 20.

Ligne 08 : on renvoie note-de-sympa, soit 20.

Ligne 12 : on stocke ce 20 dans b.

Ligne 13 : appel à sympa().

Ligne 07 : la variable note-de-sympa est affectée à 20.

Ligne 08 : on renvoie note-de-sympa, soit 20.

Ligne 13 : on stocke ce 20 dans c.

On voit donc que la valeur associée à note-du-programme-principal n'a pas été modiifiée.

Bilan : portée des variables globales

Si on résume la portée des variables globales en image, ça donne ceci :

portee-variable-globale

Nous avons vu qu'à l'intérieur d'une fonction :

  1. on peut lire une variable globale
  2. on ne peut pas modifier une variable globale
  3. on crée une nouvelle variable locale portant le même nom que la variable globale si on tente de "modifier" une variable globale à l'aide d'une affectation dans la fonction.

CONCLUSION : une fonction ne peut donc pas modifier une variable globale.

2 - Se passer des variables globales

PAS DE VARIABLES GLOBALES, sauf exception

Nous n'utiliserons quasiment jamais les lectures directes de variables globales dans nos fonctions.

Pourquoi ?

Nous verrons qu'elles posent problème lorsqu'on veut garantir et vérifier le fonctionnement correct des fonctions qui les utilisent.

Elles peuvent paraître pratiques, mais en réalité, cette utilisation pose souvent des problèmes.

Dans quel cas les utiliser alors ? Dans le cadre de NSI, on peut se limiter à ces deux cas :

  1. Pour éviter de créer des fonctions dont la plupart des paramètres recoivent des CONSTANTES globales (comme dans votre projet console qcm, jeu ou générateur de scénario)
  2. Pour permettre aux fonctions événementielles d'avoir accès à des informations non liées au widget ayant déclenché l'événement (vous verrez cela lors de l'activité Tkinter suivante)

Voici à titre d'exercice final sur les variables locales-globales, un programme qui affiche les meilleurs scores des joueurs d'un jeu. On dispose de deux tableaux :

  1. le tableau joueurs qui contient le nom des joueurs.
  2. le tableau scores qui contient les meilleurs scores des joueurs.

Le lien entre les cases se fera à l'aide des numéros d'indice bien entendu.

Nous allons réaliser plusieurs versions du programme :

  • D'abord sans lecture de variable globale depuis la fonction : c'est ce qu'il faut faire lorsque c'est possible.
  • Ensuite avec lecture directe des variables globales depuis la fonction : le programme sera plus léger mais moins propre.

Version sans lecture directe de variable globale

Le principe est de transmettre toutes données dont la fonction a besoin à travers les paramètres.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Déclaration des variables globales joueurs = ['Alice', 'Bob', 'Clark'] scores = [520, 1300, 70] # Déclaration des fonctions def afficher_scores(j, s): '''Affiche les scores des joueurs à partir des tableaux j des joueurs et s des scores''' longueur = len(j) for i in range(longueur): print(f"Le joueur {j[i]} est parvenu à atteindre {s[i]} points") # Corps du programme principal afficher_scores(joueurs, scores)

Voici ce qu'il affiche :

Le joueur Alice est parvenu à atteindre 520 points Le joueur Bob est parvenu à atteindre 1300 points Le joueur Clark est parvenu à atteindre 70 points

08° Répondre aux questions suivantes en analysant le programme précédent :

  1. j et j en ligne 8 sont-elles des paramètres locales ou globales ?
  2. Que va contenir le paramètre j de afficher_scores() lors de l'appel visible en ligne 15 ?
  3. Que va contenir le paramètre s de afficher_scores() lors de l'appel visible en ligne 15 ?
  4. La fonction afficher_scores() travaille-t-elle sur des variables locales ou des variables globales ?

...CORRECTION...

Les paramètres sont des variables locales, propres à la fonction.

Il suffit de comparer la ligne de la déclaration et la ligne de l'appel :

8 15
def afficher_scores(j, s): afficher_scores(joueurs, scores)

On voit clairement :

  1. joueurs va être stocké dans le paramètre j
  2. scores va être stocké dans le paramètre s

Cette version de la fonction travaille donc sur des variables locales (qui ont été "remplies" lors de l'appel en leur envoyant les variables globales).

09° Expliquer si les variables joueurs et scores des lignes 8 à 12 sont des variables globales ou des variables locales ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Déclaration des variables globales joueurs = ['Alice', 'Bob', 'Clark'] scores = [520, 1300, 70] # Déclaration des fonctions def afficher_scores(joueurs, scores): '''Affiche les scores des joueurs à partir des tableaux j des joueurs et s des scores''' longueur = len(joueurs) for i in range(longueur): print(f"Le joueur {joueurs[i]} est parvenu à atteindre {scores[i]} points") # Corps du programme principal afficher_scores(joueurs, scores)

...CORRECTION...

Il s'agit des paramètres de la fonction. Ce sont donc des variables locales.

Voici donc pour l'utilisation de notre premier programme. Nous pourrions d'ailleurs le lire mentalement comme ceci :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Déclaration des variables globales joueurs = ['Alice', 'Bob', 'Clark'] scores = [520, 1300, 70] # Déclaration des fonctions def afficher_scores(joueurs_de_la_fonction, scores_de_la_fonction): '''Affiche les scores des joueurs à partir des tableaux j des joueurs et s des scores''' longueur = len(joueurs_de_la_fonction) for i in range(longueur): print(f"Le joueur {joueurs_de_la_fonction[i]} est parvenu à atteindre {scores_de_la_fonction[i]} points") # Corps du programme principal afficher_scores(joueurs_du_pp, scores_du_pp)

Version avec lecture directe des variables globales

Puisqu'on ne fait que lire les données des tableaux, nous aurions pu le faire directement depuis les variables globales. Voici une nouvelle version du programme :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Déclaration des variables globales joueurs = ['Alice', 'Bob', 'Clark'] scores = [520, 1300, 70] # Déclaration des fonctions def afficher_scores(): '''Affiche les scores des joueurs à partir des tableaux joueurs et scores''' longueur = len(joueurs) for i in range(longueur): print(f"Le joueur {joueurs[i]} est parvenu à atteindre {scores[i]} points") # Corps du programme principal afficher_scores()

Voici ce qu'il affiche :

Le joueur Alice est parvenu à atteindre 520 points Le joueur Bob est parvenu à atteindre 1300 points Le joueur Clark est parvenu à atteindre 70 points

10° Répondre aux questions suivantes en analysant le programme précédent :

  1. joueurs et scores sont-ils des paramètres de la fonction ?
  2. joueurs et scores sont-ils affectés (avec un =) quelque part dans la fonction ?
  3. En déduire si joueurs et scores sont des variables globales ou des variables locales sur les lignes 8 à 12 ?

...CORRECTION...

Ici, la fonction n'a pas de paramètre donc ça ne peut pas être des paramètres...

8 15
def afficher_scores(): afficher_scores()

On remarque également qu'il n'y a aucune affectation liée à ces noms de variables dans la fonction :

8 9 10 11 12
def afficher_scores(): '''Affiche les scores des joueurs à partir des tableaux joueurs et scores''' longueur = len(joueurs) for i in range(longueur): print(f"Le joueur {joueurs[i]} est parvenu à atteindre {scores[i]} points")

Cela veut donc dire que lorsque ces variables apparaissent dans le corps de la fonction, c'est en tant que variables globales.

La fonction lit donc directement le contenu des tableaux depuis les variables globales.

Avantage de l'utilisation des variables globales : semble simplifier le code (un peu comme le fait d'avoir des fonctions avec plusieurs return).

Désavantage de l'utilisation des variables globales : comme pour les fonctions avec plusieurs return, l'utilisation de variables globales va rendre la preuve de correction de la fonction bien plus compliquée.

Bonnes pratiques de programmation

Si on résume les bonnes pratiques vues depuis le début de l'année :

  • Ne pas utiliser de lecture de variable globale dans les fonctions, sauf exception.
  • Ne pas utiliser plusieurs return dans les fonctions, sauf exception.
  • Utiliser des noms explicites
  • Créer des suites d'instructions faciles à lire et à comprendre
  • Créer des fonctions courtes (10-20 lignes maximum)
  • Respecter la décomposition des tâches en suivant le principe une tâche - une fonction

3 - FAQ

J'ai vu des programmes avec un mot-clé global. C'est quoi ?

C'est un moyen permettant à une fonction de parvenir à agir sur une variable globale, une variable définie dans le corps du programme.

Il ne faut pas l'utiliser dans le cadre d'un programme, à moins d'y être contraint.

Si vraiment vous voulez savoir comment ça fonctionne, vous pouvez lire la suite. Mais bon : vous n'aurez pas le droit de l'utiliser en NSI de toutes manières. C'est juste de la curiosité.

...CORRECTION...

Cette partie comporte des informations qui ne sont pas au programme de NSI. On va vous montrer une fois qu'on peut modifier les variables globales depuis une fonction. Ensuite, il ne faudra plus le faire. Cela pose des problèmes de stabilité et de solidité des applications réalisées.

Par contre, on trouve sur le Web de nombreux codes qui l'utilise. Bref, autant savoir que ça existe, même si son utilisation n'est pas autorisée ou conseillée.

Comment faire alors pour modifier une variable globale depuis une fonction ?

Voyons comment faire avec ce petit bout de cours fortement optionnel :

Rappel : comportement de Python par défaut
  • une fonction peut lire une variable globale.
  • une fonction ne peut pas modifier une variable globale
    • l'utilisation d'une affectation (=) crée simplement une variable locale portant le même nom.
Nouveau : modification du comportement par défaut
  • une fonction peut modifier une variable globale si on lui en donne l'autorisation :
    • en utilisant le mot-clé global suivi du nom de la variable avant de l'affecter

Mise en pratique immédiate :

1 2 3 4 5 6 7 8 9
ennemi = "Darth Vador"
def test(): global ennemi ennemi = "Palpatine"
print(ennemi) test() print(ennemi)

Ligne 7 : On affiche le contenu "Darth Vador"

Lignes 8 puis 4 et 5 : On permet à la fonction de modifier la variable en "Palpatine"

Ligne 9 : La variable globale contient bien "Palpatine" maintenant.

20° Lancer ce programme sur Python Tutor ou Thonny en mode DEBUG pour visualiser la modification du contenu de la variable.

21° Utiliser maintenant Thonny en mode DEBUG avec visualisation du tas (heap) pour connaitre le contenu ET l'id de la variable au cours de l'execution du programme. L'id de la variable globale a-t-elle été modifié après la ligne 5 ? Pour vous convaincre de la différence sans la ligne 4, vous pouvez la supprimer et relancer un nouveau debug.

CLIQUEZ ICI POUR VOIR LE CONTENU DES VARIABLES :

test :

ennemi :


Id 0x6E18 :

Id 0x3FC0 :

Id 0xB030 :

1 2 3 4 5 6 7 8 9
ennemi = "Darth Vador"
def test(): global ennemi ennemi = "Palpatine"
print(ennemi) test() print(ennemi)

Voilà, nous sommes parvenus à modifier une variable globale depuis une fonction. Attention, n'oubliez pas que la variable change d'identifiant associé, comme à chaque fois qu'on utilise une affectation  = .

Spécificités des portées en fonction des variables

La portée des variables globales et locales, ainsi que la façon dont on peut les modifier est variable en fonction du langage de programmation. Il s'agit d'une des premières choses qu'il faut regarder et comprendre lorsqu'on s'attaque à un nouveau langage. Pour vous, c'est fait avec Python maintenant.

Note finale : Limitez l'utilisation des variables globales !

La modification des variables globales avec global doit rester marginale dans vos programmes. Il est en effet difficile de maintenir fonctionnel un code qui comporte plusieurs variables modifiées par plusieurs fonctions créées par des personnes différentes.

Si vous devez utilisez des variables globales (même en lecture simple), respectez au moins ces trois points :

  1. Placer les clairement en début de code dans la partie adéquate de façon à clairement les mettre en évidence.
  2. Placer des commentaires pour expliquer à quoi elles servent
  3. Donner leur des noms suffisamment rares pour que quelqu'un ne les écrase dans une fonction par une variable locale portant le même nom : si l'une de vos variables globales se nomme x, il ne faudra pas vous plaindre si l'une des personnes de votre équipe utilise aussi ce nom dans l'une de ses fonctions !

Sachez que nous verrons plus tard des outils nous permettant de nous passer des modifications de variables globales depuis les fonctions. Vous pouvez donc les utiliser pour l'instant, mais gardez à l'esprit qu'il s'agit plutôt d'une mauvaise pratique.

On retiendra donc qu'une variable globale est une variable déclarée dans le corps du programme lui-même.

Une telle variable peut être lue mais pas modifiée depuis une fonction.

Néanmoins, on évite autant que possible de les utiliser directement depuis une fonction.

Activité publiée le 30 01 2022
Dernière modification : 30 01 2022
Auteur : ows. h.