Aller au contenu

TP - Variable locale, variable globale

De manière générale, les variables définies à l'intérieur des fonctions sont locales. Elles n'existent pas en dehors de cette fonction et ne modifient pas le contenu des autres variables du programme, extérieures à la fonction, même si celles-ci ont la même étiquette (le même « nom »).

Ce TP guidé a pour objectif de vous permettre de faire la différence entre ces différentes définitions afin d'anticiper au mieux le comportement d'un programme.

Exemple 1 - En dehors et dans une fonction

  1. Anticipez les affichages réalisés par le code ci-dessous et notez-les sur votre cahier.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    a = 2
    
    def f():
        a = 5
        print("A l'intérieur de f, a =", a)
    
    
    print("Avant d'appeler la fonction f, a =", a)
    f()
    print("Après avoir appelé la fonction f, a =", a)
    
  2. Vérifiez votre anticipation en copiant/collant/exécutant ce code.

    Affichages obtenus
    Avant d'appeler la fonction f, a = 2
    A l'intérieur de f, a = 5
    Après avoir appelé la fonction f, a = 2
    

    Vous pouvez aussi visualiser ce comportement sur le site Python tutor.

  3. Essayez d'expliquer pourquoi ces affichages ont eu lieu avant de valider votre raisonnement avec les explications ci-dessous.

    Explications des lignes 1 à 8

    Le a de la ligne 1 est dans l'espace global : il est connu en n'importe quel point du programme.

    • Après la ligne 1, la situation peut être schématisée ainsi :
    • Les lignes 3 à 5 définissent la fonction f() et ne sont pas exécutées tant que f() n'est pas appelée.
    • La ligne 8 est exécutée et affiche la valeur de l'objet portant le nom a : cette ligne affiche donc 2 (ou plutôt : « Avant d'appeler la fonction f, a = 2 »).
    Explications des lignes 9 et 10
    • En ligne 9, on fait un appel à f(). L'appel f() a pour effet de créer une étiquette a locale à f() avec l'affectation a = 5. On pourrait en fait noter cette variable a_f pour la distinguer de l'étiquette a de portée globale.
    • L'exécution de la ligne 5 (corps de f()) affiche la valeur de l'objet étiqueté a local à la fonction f().
      La fonction se termine : son espace de noms disparaît.
    • La ligne 10 affiche la valeur du seul objet de l'espace global étiqueté a.

Exemple 2 - En dehors d'une fonction

  1. Anticipez les affichages réalisés par le code ci-dessous et notez-les sur votre cahier.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    a = 2
    
    def f(x):
        print("A l'intérieur de f, a =", a)
        return x+2
    
    
    print("Avant d'appeler la fonction f, a =", a)
    f(a)
    print("Après avoir appelé la fonction f, a =", a)
    
  2. Vérifiez votre anticipation en copiant/collant/exécutant ce code.

    Affichages obtenus
    Avant d'appeler la fonction f, a = 2
    A l'intérieur de f, a = 2
    Après avoir appelé la fonction f, a = 2
    

    Vous pouvez aussi visualiser ce comportement sur le site Python tutor.

  3. Essayez d'expliquer pourquoi ces affichages ont eu lieu avant de valider votre raisonnement avec les explications ci-dessous.

    Explications des lignes 1 à 8

    Le a de la ligne 1 est dans l'espace global : il est connu en n'importe quel point du programme.

    • Après la ligne 1, la situation peut être schématisée ainsi :
    • Les lignes 3 à 5 définissent la fonction f() et ne sont pas exécutées tant que f() n'est pas appelée.
    • La ligne 8 est exécutée et affiche la valeur de l'objet portant le nom a : cette ligne affiche donc 2 (ou plutôt : « Avant d'appeler la fonction f, a = 2 »).
    Explications des lignes 9 et 10
    • En ligne 9, on fait un appel à f(). L'appel f(a) a pour effet de créer une étiquette x locale à f() avec l'affectation (d'étiquette) x = a :
    • L'exécution de la ligne 4 (corps de f()) affiche la valeur du seul objet étiqueté a.
      Remarque : l'étiquette est aussi connue de f() car l'étiquette a est dans l'espace global.
    • La ligne 5 « return x+2 » crée un objet de type int et de valeur 4 (mais sans étiquette) :
      La fonction se termine : son espace de noms disparaît.
    • La ligne 10 affiche la valeur du seul objet de l'espace global étiqueté a.

Exemple 3 - Variable locale et variable globale de même nom

  1. Anticipez les affichages réalisés par le code ci-dessous et notez-les sur votre cahier.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    a = 2
    
    def f(x):
        a = x + 3
        return a
    
    
    print("Avant d'appeler la fonction f, a =", a)
    f(a)
    print("Après avoir appelé la fonction f, a =", a)
    
  2. Vérifiez votre anticipation en copiant/collant/exécutant ce code.

    Affichages obtenus
    Avant d'appeler la fonction f, a = 2
    Après avoir appelé la fonction f, a = 2
    

    Vous pouvez aussi visualiser ce comportement sur le site Python tutor.

  3. Essayez d'expliquer pourquoi ces affichages ont eu lieu avant de valider votre raisonnement avec les explications ci-dessous.

    Explications des lignes 1 à 8

    Le a de la ligne 1 est dans l'espace global : il est connu en n'importe quel point du programme.

    • A la ligne 1, on crée une étiquette a de portée globale sur un objet de type int :
    • La ligne 8 est exécutée et affiche la valeur de l'objet portant le nom a.
    Explications des lignes 9 et 10
    • Ligne 9 : on fait un appel à la fonction f() par f(a) :

    • Ligne 4 (corps de de la fonction f()) : on créé une étiquette a locale à f(). On pourrait la noter a_f pour la distinguer de l'étiquette a de portée globale.

    • En ligne 5 (corps de de la fonction f()), l'instruction « return a » permet de conclure que l'image de 2 par f() est 5. Cependant aucune étiquette au niveau global n'est affectée à cette valeur, si bien qu'en sortie de f() on n'a plus accès à cette image car les noms locaux à f() disparaissant !
    • La ligne 10 affiche la valeur du seul objet de l'espace global étiqueté a.
    Remarque importante

    A l'intérieur de la fonction, l'étiquette a désigne ainsi un autre objet qu'à l'extérieur de la fonction (elle masque la variable a de l'espace global).
    Les opérations faites alors sur ce a local n'affectent pas le a global.

    Lorsqu'on sort de la fonction, le a local à la fonction disparaît (il n'existe que le temps d'exécution de la fonction) et c'est à nouveau le a global de la ligne 1 que l'on manipule.

    En d'autres termes, le code de notre script pourrait être réécrit comme suit de façon tout à fait équivalente:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    a = 2
    
    def f(x):
        b = x + 3
        return b
    
    print("Variable a avant appel de f:", a)
    f(a)
    print("Variable a après appel de f:", a)
    

Exemple 4 - Variable globale et paramètre de même nom

  1. Anticipez les affichages réalisés par le code ci-dessous et notez-les sur votre cahier.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    a = 2
    
    def f(a):
        a = a + 3
        return a
    
    
    print("Avant d'appeler la fonction f, a =", a)
    f(a)
    print("Après avoir appelé la fonction f, a =", a)
    
  2. Vérifiez votre anticipation en copiant/collant/exécutant ce code.

    Affichages obtenus
    Avant d'appeler la fonction f, a = 2
    Après avoir appelé la fonction f, a = 2
    

    Vous pouvez aussi visualiser ce comportement sur le site Python tutor.

  3. Essayez d'expliquer pourquoi ces affichages ont eu lieu avant de valider votre raisonnement avec les explications ci-dessous.

    Explications des lignes 1 à 8
    • A la ligne 1, on crée un objet global étiqueté a de type int et de valeur 2 :
    • Les lignes 3 à 5 ne font que définir la fonction f(). Pour le moment, elles ne sont pas exécutées.
    • La ligne suivante à être exécutée est la ligne 8. Elle affiche la valeur ciblée par a, c'est-à-dire 2.
    Explications des lignes 9 et 10

    Dans les deux cas, la valeur affichée est 2.

    Décomposons:

    • En ligne 9, l'appel est f(a). Attention, à ce stade une nouvelle étiquette a est créée, mais qui ne sera connue que de la fonction f() (on pourrait la nommer a_f pour la distinguer de l'étiquette a globale déjà créée).
      L'appel f(a) commence par une affectation a_f ← a, qui crée une nouvelle étiquette sur l'objet a :
    • On exécute ensuite la ligne 4 (dans le corps de la fonction).
      L'affectation a = a + 3 crée cette fois un nouvel objet qui recevra la valeur 2+3 et l'étiquette a. Attention, il s'agit là encore de l'étiquette locale a, c'est-à-dire de a_f :
    • La ligne 5 return a signifie que f(a) vaut 5... mais on ne s'en sert pas dans ce code en renvoyant dans la partie globale !
      La fonction se termine : tous les noms (étiquettes) locaux à f() disparaissent :

    • On reprend le déroulé du programme à la ligne 10 et on affiche la valeur du seul objet encore étiqueté a : il a pour valeur 2.

    Conséquence importante

    Le code proposé peut donc être réécrit de façon parfaitement équivalente sous la forme :

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    a = 2
    
    def f(b):
        b = b + 3
        return b
    
    
    print("Avant d'appeler la fonction f, a =", a)
    f(a)
    print("Après avoir appelé la fonction f, a =", a)
    

    Cette seconde écriture est certainement plus facile à lire car elle n'entraîne pas d'éventuelles confusions pour le lecteur entre des noms de variable de l'espace local à une fonction et des noms de variables de l'espace global.

    Retenez cela :
    En général, évitez toujours de donner le même nom à une variable globale et au paramètre d'une fonction. Cela ne fait que compliquer la lecture du code et rend ensuite difficile la recherche d'erreurs dans ce code...

Exemple 5 - Variable globale seule

  1. Anticipez les affichages réalisés par le code ci-dessous et notez-les sur votre cahier.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    a = 2
    
    def f():
        a = a + 3
        return a
    
    
    print("Avant d'appeler la fonction f, a =", a)
    f()
    print("Après avoir appelé la fonction f, a =", a)
    
  2. Vérifiez votre anticipation en copiant/collant/exécutant ce code.

    Affichages obtenus

    On obtient une erreur :

    line 4, in f
    a = a + 3
    UnboundLocalError: local variable 'a' referenced before assignment
    

    Vous pouvez aussi visualiser ce comportement sur le site Python tutor.

  3. Essayez d'expliquer pourquoi ces affichages ont eu lieu avant de valider votre raisonnement avec les explications ci-dessous.

    Explications
    • En ligne 1, on crée un objet global de type int et de valeur 2.

    • La ligne 9 fait un appel à f().

    • On exécute ensuite la ligne 4 (dans le corps de la fonction).
      L'affectation a = a + 3 est la ligne qui provoque une erreur. Pourquoi ?

    Comme on a vu que le membre de droite d'une affectation était exécuté en premier lieu, on pourrait s'attendre à ce que le a de droite désigne le a de l'espace global, puis qu'un a = a_f local à la fonction f() soit créé grâce à la partie gauche de l'instruction d'affectation.
    Cela n'a pas lieu : la présence d'un a local et d'un a global dans un même espace de noms est interdit ! En effet, la lecture du code par la machine ne permet pas d'implicite. L'opération demandée ici est donc interdite.

    Même si la partie droite de l'instruction d'affectation est effectuée en premier, une lecture globale de l'instruction à quand même lieu. Cette lecture crée immédiatement un nom a local, ce qui fait que le a de droite ne peut que désigner également ce a local.
    Puisque ce a local n'a pas encore de valeur dans la partie droite, l'erreur est affichée : referenced before assignment soit « variable référencée avant d'avoir été affectée ».

    Rappel

    Nous avons vu dans les exemples précédents une façon qui permet de contourner ce problème.
    Ainsi, les instructions suivantes ne renveront pas d'erreur (tout en étant inutile puisque, comme dans les exemples précédents, on n'utilise pas ici la valeur renvoyée par la fonction f()) :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    a = 2
    
    def f():
        b = a + 3
        return b
    
    print("Avant d'appeler la fonction f, a =", a)
    f()
    print("Après avoir appelé la fonction f, a =", a)
    

    La morale est toujours la même : évitez d'utiliser dans une fonction des noms qui peuvent prêter à confusion avec les noms de l'espace global, vous pourrez ainsi plus facilement lire et corriger votre code !