• PYTHON > portée des variables

      Dans nos fonctions, quelles variables sont accessibles ?

      >>> a = 5
      >>> def print_a():
      ...     """Fonction chargée d'afficher la variable a.
      ...     Cette variable a n'est pas passée en paramètre de la fonction.
      ...     On suppose qu'elle a été créée en dehors de la fonction, on veut voir
      ...     si elle est accessible depuis le corps de la fonction"""
      ...     print("La variable a = {0}.".format(a))
      >>> print_a()
      a variable a = 5.
      >>> a = 8
      >>> print_a()
      La variable a = 8.

       

      La variable a n’est pas passée en paramètre de la fonctionprint_a. Et pourtant, Python la trouve, tant qu’elle a été définie avant l’appel de la fonction.

      C’est là qu’interviennent les différents espaces.

      L’espace local

      Dans votre fonction, quand vous faites référence à une variablea, Python vérifie dans l’espace local de la fonction. Cet espace contient les paramètres qui sont passés à la fonction et les variables définies dans son corps. Python apprend ainsi que la variablean’existe pas dans l’espace local de la fonction. Dans ce cas, il va regarder dans l’espace local dans lequel la fonction a été appelée. Et là, il trouve bien la variableaet peut donc l’afficher.

      D’une façon générale, je vous conseille d’éviter d’appeler des variables qui ne sont pas dans l’espace local, sauf si c’est nécessaire. Ce n’est pas très clair à la lecture ; dans l’absolu, préférez travailler sur des variables globales, cela reste plus propre (nous verrons cela plus bas). Pour l’instant, on ne s’intéresse qu’aux mécanismes, on cherche juste à savoir quelles variables sont accessibles depuis le corps d’une fonction et de quelle façon.

      La portée de nos variables

      Voyons quelques cas concrets. Je vais les expliquer au fur et à mesure, ne vous en faites pas.

       

      Qu’advient-il des variables définies dans un corps de fonction ?

      Voyons un nouvel exemple :

      def set_var(nouvelle_valeur):
      
          """Fonction nous permettant de tester la portée des variables
          définies dans notre corps de fonction"""
          # On essaye d'afficher la variable var, si elle existe
      
          try:
              print("Avant l'affectation, notre variable var vaut {0}.".format(var))
      
          except NameError:
              print("La variable var n'existe pas encore.")
      
          var = nouvelle_valeur
          print("Après l'affectation, notre variable var vaut {0}.".format(var))

      Et maintenant, utilisons notre fonction :

      >>> set_var(5)
      La variable var n'existe pas encore.
      Après l'affectation, notre variable var vaut 5.
      >>> var
      Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
      NameError: name 'var' is not defined
      

      Je sens que quelques explications s’imposent :

      • Lors de notre appel àset_var, notre variablevarn’a pu être trouvée par Python : c’est normal, nous ne l’avons pas encore définie, ni dans notre corps de fonction, ni dans le corps de notre programme. Python affecte la valeur5à la variablevar, l’affiche et s’arrête.

      • Au sortir de la fonction, on essaye d’afficher la variablevar… mais Python ne la trouve pas ! En effet : elle a été définie dans le corps de la fonction (donc dans son espace local) et, à la fin de l’exécution de la fonction, l’espace est détruit… donc la variablevar, définie dans le corps de la fonction, n’existe que dans ce corps et est détruite ensuite.

      Python a une règle d’accès spécifique aux variables extérieures à l’espace local : on peut les lire, mais pas les modifier. C’est pourquoi, dans notre fonctionprint_a, on arrivait à afficher une variable qui n’était pas comprise dans l’espace local de la fonction. En revanche, on ne peut modifier la valeur d’une variable extérieure à l’espace local, par affectation du moins. Si dans votre corps de fonction vous faitesvar = nouvelle_valeur, vous n’allez en aucun cas modifier une variable extérieure au corps.

      En fait, quand Python trouve une instruction d’affectation, comme par exemplevar = nouvelle_valeur, il va changer la valeur de la variable dans l’espace local de la fonction. Et rappelez-vous que cet espace local est détruit après l’appel à la fonction.

      Pour résumer, et c’est ce qu’il faut retenir, une fonction ne peut modifier, par affectation, la valeur d’une variable extérieure à son espace local.

      Cela paraît plutôt stupide au premier abord… mais pas d’impatience. Je vais relativiser cela assez rapidement.

      Une fonction modifiant des objets

      J’espère que vous vous en souvenez, en Python, tout est objet. Quand vous passez des paramètres à votre fonction, ce sont des objets qui sont transmis. Et pas les valeurs des objets, mais bien les objets eux-mêmes, ceci est très important.

      Bon. On ne peut affecter une nouvelle valeur à un paramètre dans le corps de la fonction. Je ne reviens pas là-dessus. En revanche, on pourrait essayer d’appeler une méthode de l’objet qui le modifie… Voyons cela :

      >>> def ajouter(liste, valeur_a_ajouter):
      ...     """Cette fonction insère à la fin de la liste la valeur que l'on veut ajouter"""
      ...     liste.append(valeur_a_ajouter)
      ...
      >>> ma_liste=['a', 'e', 'i']
      >>> ajouter(ma_liste, 'o')
      >>> ma_liste
      ['a', 'e', 'i', 'o']
      >>>

      Cela marche ! On passe en paramètres notre objet de typelistavec la valeur à ajouter. Et la fonction appelle la méthodeappendde l’objet. Cette fois, au sortir de la fonction, notre objet a bel et bien été modifié.

      Je vois pas pourquoi. Tu as dit qu’une fonction ne pouvait pas affecter de nouvelles valeurs aux paramètres ?

      Absolument. Mais c’est cela la petite subtilité dans l’histoire : on ne change pas du tout la valeur du paramètre, on appelle juste une méthode de l’objet. Et cela change tout. Si vous vous embrouillez, retenez que, dans le corps de fonction, si vous faitesparametre = nouvelle_valeur, le paramètre ne sera modifié que dans le corps de la fonction. Alors que si vous faitesparametre.methode_pour_modifier(…), l’objet derrière le paramètre sera bel et bien modifié.

      On peut aussi modifier les attributs d’un objet, par exemple changer une case de la liste ou d’un dictionnaire : ces changements aussi seront effectifs au-delà de l’appel de la fonction.

      Et les références, dans tout cela ?

      J’ai parlé des références, et vous ai promis d’y consacrer une section ; c’est maintenant qu’on en parle !

      Je vais schématiser volontairement : les variables que nous utilisons depuis le début de ce cours cachent en fait des références vers des objets.

      Concrètement, j’ai présenté les variables comme ceci : un nom identifiant pointant vers une valeur. Par exemple, notre variable nomméeapossède une valeur (disons 0).

      En fait, une variable est un nom identifiant, pointant vers une référence d’un objet. La référence, c’est un peu sa position en mémoire. Cela reste plus haut niveau que les pointeurs en C par exemple, ce n’est pas vraiment la mémoire de votre ordinateur. Et on ne manipule pas ces références directement.

      Cela signifie que deux variables peuvent pointer sur le même objet.

      Bah… bien sûr, rien n’empêche de faire deux variables avec la même valeur.

      Non non, je ne parle pas de valeurs ici mais d’objets. Voyons un exemple, vous allez comprendre :

      >>> ma_liste1 = [1, 2, 3]
      >>> ma_liste2 = ma_liste1
      >>> ma_liste2.append(4)
      >>> print(ma_liste2)
      [1, 2, 3, 4]
      >>> print(ma_liste1)
      [1, 2, 3, 4]
      

      Nous créons une liste dans la variablema_liste1. À la ligne 2, nous affectonsma_liste1à la variablema_liste2. On pourrait croire quema_liste2est une copie dema_liste1. Toutefois, quand on ajoute4àma_liste2,ma_liste1est aussi modifiée.

      On dit quema_liste1etma_liste2contiennent une référence vers le même objet : si on modifie l’objet depuis une des deux variables, le changement sera visible depuis les deux variables.

      Euh… j’essaye de faire la même chose avec des variables contenant des entiers et cela ne marche pas.

      C’est normal. Les entiers, les flottants, les chaînes de caractères, n’ont aucune méthode travaillant sur l’objet lui-même. Les chaînes de caractères, comme nous l’avons vu, ne modifient pas l’objet appelant mais renvoient un nouvel objet modifié. Et comme nous venons de le voir, le processus d’affectation n’est pas du tout identique à un appel de méthode.

      Et si je veux modifier une liste sans toucher à l’autre ?

      Eh bien c’est impossible, vu comment nous avons défini nos listes. Les deux variables pointent sur le même objet par jeu de références et donc, inévitablement, si vous modifiez l’objet, vous allez voir le changement depuis les deux variables. Toutefois, il existe un moyen pour créer un nouvel objet depuis un autre :

      >>> ma_liste1 = [1, 2, 3]
      >>> ma_liste2 = list(ma_liste1) # Cela revient à copier le contenu de ma_liste1
      >>> ma_liste2.append(4)
      >>> print(ma_liste2)
      [1, 2, 3, 4]
      >>> print(ma_liste1)
      [1, 2, 3]
      >>>

      À la ligne 2, nous avons demandé à Python de créer un nouvel objet basé surma_liste1. Du coup, les deux variables ne contiennent plus la même référence : elles modifient des objets différents. Vous pouvez utiliser la plupart des constructeurs (c’est le nom qu’on donne àlistpour créer une liste par exemple) dans ce but. Pour des dictionnaires, utilisez le constructeurdicten lui passant en paramètre un dictionnaire déjà construit et vous aurez en retour un dictionnaire, semblable à celui passé en paramètre, mais seulement semblable par le contenu. En fait, il s’agit d’une copie de l’objet, ni plus ni moins.

      Pour approcher de plus près les références, vous avez la fonctionidqui prend en paramètre un objet. Elle renvoie la position de l’objet dans la mémoire Python sous la forme d’un entier (plutôt grand). Je vous invite à faire quelques tests en passant divers objets en paramètre à cette fonction. Sachez au passage queiscompare les ID des objets de part et d’autre et c’est pour cette raison que je vous ais mis en garde quant à son utilisation.

      >>> ma_liste1 = [1, 2]
      >>> ma_liste2 = [1, 2]
      >>> ma_liste1 == ma_liste2 # On compare le contenu des listes
      True
      >>> ma_liste1 is ma_liste2 # On compare leur référence
      False
      >>>

      variables globales

      Il existe un moyen de modifier, dans une fonction, des variables extérieures à celle-ci. On utilise pour cela des variables globales.

       

      On déclare dans le corps de notre programme, donc en dehors de tout corps de fonction, une variable, tout ce qu’il y a de plus normal. Dans le corps d’une fonction qui doit modifier cette variable (changer sa valeur par affectation), on déclare à Python que la variable qui doit être utilisée dans ce corps est globale.

      Python va regarder dans les différents espaces : celui de la fonction, celui dans lequel la fonction a été appelée… ainsi de suite jusqu’à mettre la main sur notre variable. S’il la trouve, il va nous donner le plein accès à cette variable dans le corps de la fonction.

      Cela signifie que nous pouvons y accéder en lecture (comme c’est le cas sans avoir besoin de la définir comme variable globale) mais aussi en écriture. Une fonction peut donc ainsi changer la valeur d’une variable directement.

      Mais assez de théorie, voyons un exemple.

      Utiliser concrètement les variables globales

      Pour déclarer à Python, dans le corps d’une fonction, que la variable qui sera utilisée doit être considérée comme globale, on utilise le mot-cléglobal. On le place généralement après la définition de la fonction, juste en-dessous de ladocstring, cela permet de retrouver rapidement les variables globales sans parcourir tout le code (c’est une simple convention). On précise derrière ce mot-clé le nom de la variable à considérer comme globale :

       

      >>> i = 4 # Une variable, nommée i, contenant un entier
      >>> def inc_i():
      ...     global i # Python recherche i en dehors de l'espace local de la fonction
      ...     i += 1
      ...
      >>> i
      4
      >>> inc_i()
      >>> i
      5
      

       

      global i, Python permet l’accès en lecture et en écriture à cette variable.

      En résumé

      • Les variables locales définies avant l’appel d’une fonction seront accessibles, depuis le corps de la fonction, en lecture seule.

      • Une variable locale définie dans une fonction sera supprimée après l’exécution de cette fonction.

      • On peut cependant appeler les attributs et méthodes d’un objet pour le modifier durablement.

      • Les variables globales se définissent à l’aide du mot-cléglobalsuivi du nom de la variable préalablement créée.

      • Les variables globales peuvent être modifiées depuis le corps d’une fonction (à utiliser avec prudence).

 

Aucun commentaire

 

Laissez un commentaire