La portée des variables

Une variable peut être "connue" dans une partie d'un programme et inconnue dans une autre partie : la "zone" dans laquelle la variable est connue est la portée (en anglais : scope) de la variable. Dans un même programme, deux variables peuvent avoir le même nom, pourvu que leurs portées respectives n'interfèrent pas.

ExempleExemple 1

1
def f() :
2
    a = 2
3
    print("Valeur de a dans le corps de la fonction : {} ".format(a))
4
    print('-----------------')
5
	
6
a = 5
7
8
print("Valeur de a dans la partie principale,")
9
print("avant appel de la fonction f : {}".format(a))
10
print('-----------------')
11
12
f()
13
	
14
print("Valeur de a dans la partie principale,")
15
print("après appel de la fonction f : {}".format(a))

On obtient :

1
Valeur de a dans la partie principale,
2
avant appel de la fonction f : 5
3
-----------------
4
Valeur de a dans le corps de la fonction : 2 
5
-----------------
6
Valeur de a dans la partie principale,
7
après appel de la fonction f : 5

La variable a de la partie principale n'est pas affectée par l'instruction a=2 de la fonction. Lorsque Python rencontre une telle assignation à l'intérieur d'une fonction, il crée une variable locale à la fonction, qui n'interfère pas avec la variable de même nom extérieure à la fonction.

En d'autres termes, tout se passe comme si le programme était le suivant :

1
def f() :
2
	b = 2
3
	print("Valeur de a dans le corps de la fonction : {} ".format(b))
4
	print('-----------------')
5
	
6
a = 5
7
8
print("Valeur de a dans la partie principale,")
9
print("avant appel de la fonction f : {}".format(a))
10
print('-----------------')
11
12
f()
13
	
14
print("Valeur de a dans la partie principale,")
15
print("après appel de la fonction f : {}".format(a))

Fondamental

Ce principe sera utilisé en permanence même sans y penser. Imaginez qu'un tel principe de portée n'existe pas : lorsqu'on écrit un gros programme, on devrait sans cesse se demander si le nom que l'on est en train d'introduire pour une simple petite routine est déjà utilisé ou non. Il est évident que l'on aurait très vite des erreurs et des variables interférant ; ou des noms de variables peu adaptés...

ExempleExemple 2

On reprend ici l'idée du programme précédent et on cherche à modifier, dans la fonction f, la variable a de la partie principale :

1
def f() :
2
    a = a+3
3
    print("Valeur de a dans le corps de la fonction : {} ".format(a))
4
    print('-----------------')
5
6
a = 5
7
8
print("Valeur de a dans la partie principale,")
9
print("avant appel de la fonction f : {}".format(a))
10
print('-----------------')
11
12
f()
13
	
14
print("Valeur de a dans la partie principale,")
15
print("après appel de la fonction f : {}".format(a))

On obtient :

1
Valeur de a dans la partie principale,
2
avant appel de la fonction f : 5
3
-----------------
4
Traceback (most recent call last):
5
  File "sans titre.py", line 36, in <module>
6
    f()
7
  File "sans titre.py", line 25, in f
8
    a = a+3
9
UnboundLocalError: local variable 'a' referenced before assignment

Et sur les versions plus récentes :

1
UnboundLocalError: cannot access local variable 'a' where it is not associated with a value

Il est ainsi confirmé que la variable a de la partie principale ne peut pas être modifiée dans f. Si l'on veut malgré tout que a soit modifiée par la fonction f, le mieux est d'utiliser un paramètre et une valeur de retour. Par exemple :

1
def f(x) :
2
    x = x+3
3
    return x
4
	
5
a = 5
6
print( 'Valeur de a : {}'.format(a) )
7
a = f(a)
8
print( 'Valeur de a : {}'.format(a) )

On obtient cette fois :

1
Valeur de a : 5
2
Valeur de a : 8

ExempleExemple 3

On a, malgré ce qui précède, le comportement suivant :

1
def f() :
2
    print("Valeur de a dans le corps de la fonction : {} ".format(a))
3
    print('-----------------')
4
	
5
a = 5
6
7
print("Valeur de a dans la partie principale,")
8
print("avant appel de la fonction f : {}".format(a))
9
print('-----------------')
10
11
f()
12
	
13
print("Valeur de a dans la partie principale,")
14
print("après appel de la fonction f : {}".format(a))
1
Valeur de a dans la partie principale,
2
avant appel de la fonction f : 5
3
-----------------
4
Valeur de a dans le corps de la fonction : 5 
5
-----------------
6
Valeur de a dans la partie principale,
7
après appel de la fonction f : 5

Ainsi, dans le cas où l'on ne cherche pas à affecter une variable a localement à la fonction, la variable a globale semble connue (mais non disponible pour une affectation).

ComplémentLe mot clef 'global'

1
def f() :
2
    global a
3
    a = a+3
4
    print("Valeur de a dans le corps de la fonction : {} ".format(a))
5
    print('-----------------')
6
	
7
a = 5
8
9
print("Valeur de a dans la partie principale,")
10
print("avant appel de la fonction f : {}".format(a))
11
print('-----------------')
12
13
f()
14
	
15
print("Valeur de a dans la partie principale,")
16
print("après appel de la fonction f : {}".format(a))

On obtient cette fois-ci :

1
Valeur de a dans la partie principale,
2
avant appel de la fonction f : 5
3
-----------------
4
Valeur de a dans le corps de la fonction : 8 
5
-----------------
6
Valeur de a dans la partie principale,
7
après appel de la fonction f : 8
8

AttentionGlobalement, global c'est mal

Attention : vous éviterez autant que possible l'utilisation d'une telle variable globale.

L'usage du mot clef global dans vos programmes sera à proscrire, sauf cas très particulier que vous devrez être capable de justifier.

Complément

Pour compléter la lecture de ce cours, lisez la page du cours d'openclassroom sur le sujet.