Utilisation des portées en Python

Durée : 1 h

Environnement de travail : REPL.IT

Contexte

La variable fait partie des fondamentaux de la programmation. En effet, elle va permettre de contenir une ou plusieurs données non figées, données qui seront alors traitées par le programme. En réalité, on peut classer les variables en deux catégories au sein d’un programme : les variables dites « locales » et les variables dites « globales ». Une variable locale est utilisable uniquement au sein de la fonction dans laquelle elle est définie. Une variable globale, quant à elle, est utilisable partout dans le programme.

On appelle « portée d'une variable » l'espace de code dans lequel cette variable est utilisable. On parle donc soit de portée globale, soit de portée locale. L’utilisation de portées locales, c’est-à-dire d’espaces dans le code qui délimitent le champ d’utilisation d’une variable, réduit le nombre de lignes de code qui peuvent être à l'origine de bugs. Si votre programme ne contenait que des variables globales, il serait plus difficile de retrouver les lignes qui posent un problème. En fait, vous sauriez rapidement que l’erreur se trouve dans une portée locale, dont vous connaissez l’emplacement exact ! C’est vrai, l’utilisation seule de variables globales, donc utilisables dans la portée globale, c’est-à-dire dans tout le programme, est relativement efficace dans un court programme. Mais imaginons un programme de 1 000 lignes. Il sera beaucoup plus difficile de cibler l’origine d’un bug si on ne crée pas de variables locales ayant un champ d’utilisation limité dans une portée locale.

L’objectif de ce cours est donc de comprendre concrètement comment utiliser les différentes portées dans un programme Python. Nous parlerons aussi du passage par référence en seconde partie.

Une portée locale est créée à chaque fois qu’une fonction est appelée. Toutes les variables attribuées dans cette fonction existent dans la portée locale. Lorsque la fonction renvoie les données, la portée locale est détruite et ces variables sont oubliées. La prochaine fois que vous appellerez cette fonction, les variables locales ne conserveront pas les valeurs qui y étaient précédemment stockées pendant le dernier appel de la fonction.

Quelles sont les caractéristiques des portées locales ?

  • Le code dans le contexte global ne peut pas utiliser de variables appartenant à une portée locale.

  • Une portée locale peut accéder aux variables globales.

  • Le code dans la portée locale d’une fonction ne peut pas utiliser de variables d’une autre portée locale.

  • Vous pouvez utiliser le même nom pour différentes variables si elles sont de portées différentes, cependant c’est une pratique déconseillée.

Complément

Considérez la portée comme un conteneur pour les variables. Lorsqu’une portée est détruite, toutes les valeurs stockées dans les variables de la portée sont oubliées. Il n’y a qu’une seule portée globale et elle est créée lorsque votre programme commence. Quand votre programme se termine, le contexte global est détruit et toutes ses variables effacées. Si ce n’était pas le cas, les variables conserveraient leur valeur à chaque nouvelle exécution du programme.

Considérons quelques exemples pratiques :

Les variables locales ne peuvent pas être utilisées dans la portée globale

1
def spam():
2
    eggs = 3131
3
spam()
4
print(eggs)

Le terminal renvoie alors l’erreur suivante :

1
Traceback (most recent call last):
2
  File "doc.py", line 4, in <module>
3
    print(eggs)
4
NameError: name 'eggs' is not defined

L’erreur se produit car la variable eggs n’existe que dans la portée locale lors de l'appel de spam(). La fonction spam() renvoie alors ses données, sa portée locale est donc détruite, et il n’y a plus de variable nommée eggs dans le programme (sa portée étant détruite). Ainsi, lorsque votre programme essaie d’exécuter print(eggs), Python indique que la variable eggs n’est pas définie puisqu’elle n’existe plus.

Attention

Seules les variables globales peuvent être utilisées dans la portée globale.

Une portée locale ne peut pas utiliser une variable appartenant à une autre portée locale

1
def spam():
2
    eggs = 99
3
    bacon()
4
    print(eggs)
5
def bacon():
6
    ham = 101
7
    eggs = 0
8
spam()

Lorsque le programme démarre, la fonction spam() est définie. Dans cette fonction, une portée locale est créée. La variable locale eggs est définie sur 99. Puis la fonction bacon() (définie plus bas) est appelée et une deuxième portée locale est créée au sein de cette seconde fonction.

Plusieurs portées locales peuvent exister en même temps. Dans cette nouvelle portée locale, la variable locale ham est définie sur 101 et la variable locale eggs (variable différente de celle appartenant à la portée locale de la fonction spam()) est également créée et définie sur « 0 ».

Lorsque bacon() retourne sa valeur, la portée locale de cet appel est détruite (cela veut donc dire que la variable eggs appartenant à la fonction spam() est elle aussi détruite). Le programme poursuit l'exécution dans la fonction spam() pour imprimer la valeur de eggs. Comme la portée locale de l’appel spam() existe toujours ici, la variable eggs est toujours définie sur « 99 ».

Le programme imprime donc « 99 ».

Pour faire simple, on peut dire que dans ce programme, les deux variables eggs sont deux variables différentes étant chacune dans une portée locale différente. Leurs valeurs sont indépendantes l’une de l’autre. Donc, lorsque la fonction bacon() est appelée et que sa portée est détruite, alors les variables ham et eggs appartenant à bacon() sont détruites. Mais à ce moment-là, la variable eggs créée dans la fonction spam() n’est pas détruite, puisque ce n’est pas la même variable car elle n’appartient pas à la même portée.

Les variables globales peuvent être lues et utilisées dans la portée locale

1
def spam():
2
    print(eggs)
3
eggs = 42
4
spam()
5
print(eggs)

Dans cet exemple, puisqu’il n’y a aucun paramètre nommé eggs (dans les parenthèses lors de la définition de la fonction spam()), ni aucune ligne de code qui crée la variable locale eggs dans la fonction spam(), alors, lorsque eggs est utilisée dans spam(), Python considère qu’il s’agit d’une référence à la variable globale eggs. C’est pourquoi « 42 » est imprimé lorsque le programme est exécuté.

Variables locales et globales avec le même nom

Pour vous simplifier la vie, évitez d'utiliser des variables locales ayant le même nom que des variables globales ou que d’autres variables locales (même si techniquement, c'est parfaitement possible de le faire en Python comme le montre cet exemple).

1
def spam():
2
    eggs = "spam local" # 1.
3
    print(eggs) # Imprime "spam local"
4
def bacon():
5
    eggs = "bacon local" # 2.
6
    print(eggs) # Imprime "bacon local"
7
    spam()
8
    print(eggs) # Imprime "bacon local"
9
eggs = "global" # 3.
10
bacon()
11
print(eggs) # Imprime "global"

Il existe en fait trois variables différentes dans ce programme, mais elles sont toutes nommées eggs. Les variables sont les suivantes :

  1. Une variable nommée eggs qui existe dans une portée locale lorsque spam() est appelée.

  2. Une variable nommée eggs qui existe dans une portée locale lorsque bacon() est appelée.

  3. Une variable nommée eggs qui existe dans la portée globale.

Étant donné que ces trois variables distinctes ont toutes le même nom, il peut être déroutant de savoir laquelle est utilisée à un moment donné. C'est pourquoi il vaut mieux éviter d'utiliser le même nom de variable dans différentes portées.

La déclaration « global »

1
def spam():
2
    global eggs
3
    eggs = "spam"
4
eggs = "global"
5
spam()
6
print(eggs)

Si vous devez modifier une variable globale à partir d'une fonction, utilisez la déclaration globale. Ici, la ligne de code, qui dans la fonction spam() indique global eggs, dit à Python :

« Dans cette fonction, eggs se réfère à la variable globale, donc ne crée pas une variable locale avec ce nom. »

1
def spam():
2
    global eggs
3
    eggs = "spam"
4
eggs = "global"
5
spam()
6
print(eggs)

Lorsque vous exécutez ce programme, que va afficher l'appel final print() ? On peut voir que dans la fonction spam(), la variable eggs n’est pas créée car la commande globale indique qu’on se réfère à la variable globale eggs qui est déclarée dans la portée globale (la valeur « global » lui est affectée).

Donc, dans la fonction spam(), on change la valeur de la variable globale eggs pour lui donner la valeur « spam » . Ce code s’exécute lorsque la fonction spam() est déclarée. La commande print(eggs) affiche donc la variable globale eggs qui est maintenant égale à « spam ».

Il existe quatre règles pour indiquer si une variable est dans une portée locale ou globale :

  1. Si une variable est utilisée dans la portée globale (c'est-à-dire en dehors de toute fonction), alors c'est toujours une variable globale.

  2. S'il existe dans une fonction une instruction globale pour cette variable, il s'agit d'une variable globale.

  3. Si dans la fonction, la variable est utilisée dans une instruction d'affectation, c'est une variable locale.

  4. Si la variable n'est pas utilisée dans une instruction d'affectation, c'est une variable globale.

Pour mieux comprendre ces règles, voici un exemple de programme :

ExempleQuatre règles pour déterminer si une variable est globale ou locale

1
def spam():
2
    global eggs
3
    eggs = "spam" # Variable globale
4
def bacon():
5
    eggs = "bacon" # Variable locale
6
def ham():
7
    print(eggs) # Variable globale
8
eggs = 42 # Variable globale
9
spam()
10
print(eggs)
  • Dans la portée globale, la variable eggs est une variable globale. Elle est initialement définie sur 42.

  • Dans la fonction spam(), eggs est la variable globale de eggs car il y a une déclaration « global » au début de la fonction.

  • Dans bacon(), eggs est une variable locale car il y a une instruction d'affectation dans cette fonction.

  • Dans la fonction ham(), eggs est la variable globale eggs car il n'y a pas d’instruction d'affectation dans cette fonction.

Dans une fonction, une variable sera toujours globale ou bien toujours locale.

Erreur dans l’ordre des instructions

Si comme dans le programme suivant, vous essayez d’utiliser une variable locale dans une fonction avant de lui attribuer une valeur, Python affichera une erreur.

1
def spam():
2
    print(eggs) # Erreur
3
    eggs = "spam local"
4
eggs = "global"
5
spam()

Le terminal renvoie :

1
Traceback (most recent call last):
2
  File "doc.py", line 5, in <module>
3
    spam()
4
  File "doc.py", line 2, in spam
5
    print(eggs) # Erreur
6
UnboundLocalError: local variable 'eggs' referenced before assignmentà retenir 

Cette erreur se produit car Python voit qu’il y a une affectation pour eggs dans la fonction spam() et considère donc eggs comme locale. Puisque la commande print(eggs) est exécutée avant que eggs ne soit affectée à quoi que ce soit, la variable locale eggs n'existe pas. Python ne recourt pas à la variable globale eggs.