Formation I.S.N.

Variables locales, variables globales

Plus tard ! Dans ce chapitre, nous rappelons la différence entre variables locales et globales au travers de divers exemples à tester et à modifier.
Nous vous conseillons de le traiter plus tard sauf si vous avez déjà terminé le reste des exercices proposés.

Variables locales

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 le même «nom».

Dans l'exemple ci-dessous,

  • une variable a est utilisée en variable locale, à l'intérieur de la fonction f() ;
  • une seconde variable, portant également le nom a, est utilisée en variable globale, dans le programme principal.

					
					
  1. Lancer le programme précédent pour voir ses effets.
  2. Modifier l'affectation de a dans la fonction f par a = a+2. Obtient-on le résultat attendu ?
  3. Modifier la fonction f pour que a soit vraiment augmenté de 2 après l'appel à f.
  • Solution du 2°/ ?
  • Solution du 3°/ ?
Même en remplaçant l'instruction a = 2 par a = a+2, le comportement ne change pas : a reste une variable locale à la fonction.

##----- Définition des Fonctions -----##
def f(a):
    """Cette fonction affiche du texte."""
    a = a+2
    print('Dans la fonction f, a = {}'.format(a))
    return a

##----- Programme principal -----##
a = 5
print('Avant d\'appeler la fonction f, a = {}'.format(a))
a = f(a)
print('Apres appel de la fonction f, a = {}'.format(a))
						

Manipuler une variable globale

Une fonction peut modifier une variable globale, quel que soit le type de cette variable.

Pour cela, il faut que celle-ci soit déclarée explicitement global dans la définition de la fonction.

En déclarant globale la variable a dans la fonction f du programme précédent, on obtiendra la comportement désiré :


					
					

Attention !

Déclarer une variable global dans une fonction est rarement nécessaire pour de «petits» programmes. Il faut essayer de s'en passer autant que possible car les fonctions doivent, au maximum, être indépendantes du programme qui y fait appel.

Cas des listes

De manière générale, les paramètres des fonctions sont considérés comme des variables locales à la fonction. Est-ce toujours le cas avec une liste en paramètre plutôt qu'avec un nombre ?

Programme n°1


					
					

Programme n°2


					
					

Questions

  1. Tester ces deux programmes. Quelles remarques peut-on faire ?
  2. Au lieu d'utiliser la méthode .append(), quelle autre instruction permettrait d'ajouter l'élément 2 en fin de liste ?
  3. Modifier les deux programmes précédents à l'aide de cette instruction. Les remarques de la première question restent-elles valables ?
  • Programme n°1 modifié ?
  • Programme n°2 modifié ?

##----- Définition des Fonctions -----##
def f(A):
    A = A+[2]
    print('Dans f, A = {}'.format(A))

##---- Partie principale ----##
A = [5]
print('Avant f, A = {}'.format(A))
f(A)
print('Après f, A = {}'.format(A))
						

##----- Définition des Fonctions -----##
def f(X):
    X = X+[2]
    print('Dans f, X = {}'.format(X))

##---- Partie principale ----##
A = [5]
print('Avant f, A = {}'.format(A))
f(A)
print('Après f, A = {}'.format(A))
						

Copie de listes

Lorsqu'une fonction fait appel à une liste en paramètre, elle utilise en fait un alias de cette liste (une référence sur cette liste). Comment effectuer une réelle copie de liste ?

Programme n°1


					
					

Programme n°2


					
					

Programme n°3


					
					

Programme n°4

Ce programme doit afficher deux listes :

  • la première contenant les entiers de 0 à 10 ;
  • la seconde contenant les entiers de 0 à 9 suivis de 11.

Est-ce le cas ?


					
					

Conséquence : notion d'objets

En Python, toutes les variables, quel que soit leur type, sont des objets. Contrairement aux langages C ou Java, le choix a été fait d'un typage dynamique pour rendre la manipulation de ces objets plus « facile ». En contrepartie, dans le cas d'un objet complexe, mutable et potentiellement lourd en mémoire, c'est l'action la plus rapide qui est privilégiée par le langage.

Le type list fait partie de ces objets potentiellement lourds. Pour éviter une occupation mémoire inutile, l'affectation « = » crée un nouvel alias de la liste plutôt qu'une nouvelle liste.

Ainsi, affecter une liste avec l'instruction A = [5] met le nombre entier 5 à l'intérieur d'un objet de type list puis donne la référence A à cet objet. Il y a bien deux informations distinctes à prendre en compte :

  • la référence vers l'objet de type list ;
  • les éléments que l'objet contient.

En conclusion, l'instruction B = A ne crée pas une copie de la liste référencée par A mais donne un 2ème alias (nom) au même objet.

A retenir

La fonction id() permet d'afficher l'identifiant numérique (choisi arbitrairement - pour simplifier) de l'emplacement mémoire d'un objet. Utilisons cette fonction dans les programmes précédents :

  • Programme n°2 : La méthode .append() modifie la liste référencée sans la changer d'emplacement mémoire. Puisque les alias B et A font référence au même objet, toute modification de l'objet référencée se répercute sur les deux alias :
    
    							
    							
  • Programme n°3 : L'affectation «=» suivie d'une concaténation «+» crée un nouvel objet liste pour la référence affectée. La référence B concerne un nouvel objet :
    
    							
    							

Astuce

Pour éviter ces inconvénients, il faut faire appel à la fonction deepcopy() du module copy.
Ci-dessous, la liste B sera vraiment une nouvelle liste...


from copy import *
A = [5]
B = deepcopy(A)
B.append(2)
print('A = {}'.format(A))
print('B = {}'.format(B))
				

Pour aller plus loin

Le programme ci-dessous passe en revue différentes situations concernant les listes.
Étudier les différents affichages et proposer une explication pour chacun d'eux.

  • Programme à copier et à tester
  • Explications

##-----Importation du module copy-----##
from copy import *

##-----Création de  variables-----##
A = [1,2,3]
C = [5,6,7]
L1 = [0] * 3
L2 = [[0]] * 3
L3 = [0,1,[4,5]]

##-----Définition de fonctions-----##
def f1():
    B.append(7)

def f2():
    B = [3]

def f3(X):
    B = X

def f4(X):
    global B
    B = X

def f5(X):
    X.append(1)


##-----Programme principal-----##


# Opérateurs "=" , "+", "+="  et méthode append()
B = A
print("1 : ",A, B )

B.append(4)
print("2 : ",A, B )

B = C
print("3 : ",A,B)

B = A
B = A + [5]
print("4 : ",A,B)

B = A
B = B + [1]
print("5 : ",A,B)

B = A
B += [1]
print("6 : ",A,B)

# Appels de fonctions
A = [1,2,3]
B = A
f1()
print("7 : ",A,B)

A = [1,2,3]
B = A
f2()
print("8 : ",A,B)

A = [1,2,3]

B = A
C = [5,6,7]
f3(C)
print("9 : ",A,B)

A = [1,2,3]
C = [5,6,7]
B = A
f4(C)
print("10 : ",A,B)

A = [1,2,3]
f5(A)
print("11 : ",A)



# Opérateur "*"
L1[1] +=1
print("12 : ",L1)

L2[1][0] +=1
print("13 : ",L2)


# Fonctions list(), copy() et deepcopy()
B = list(L3)
B.append(7)
print("14 : ",L3,B)

B[2].append(9)
print("15 : ",L3,B)

L3 = [0,1,[4,5]]
B = copy(L3)
B.append(7)
print("16 : ",L3,B)

B[2].append(9)
print("17 : ",L3,B)

L3 = [0,1,[4,5]]
B = deepcopy(L3)
B[2].append(11)
print("18 : ",L3,B)
  1. L'instruction A = [1,2,3] assigne à la variable A une référence vers la liste [1,2,3].
    L'instruction B = A effectue une copie de la référence de la liste (et non de la liste elle même) et donc B devient un alias de A.
  2. B étant un alias de A, toute modification de la liste est répercutée à la fois sur A et sur B.
  3. L'opérateur d'affectation = transforme B en un alias de C (et donc B n'a plus de lien avec A, la liste affectée à A ne change pas).
  4. La référence de B correspond à la liste A + [5]. A et B n'ont plus de lien et A n'est pas affecté par l'opérateur de concaténation +.
  5. La référence de B correspond à la liste B + [1]. A et B n'ont plus de lien.
  6. L'opérateur += est équivalent à .append([]) pour les listes.
  7. B est un alias de A. Il n'y a pas de variable locale B dans le scope de la fonction f1() : la méthode .append(7) s'applique à la variable B.
  8. On définit une variable locale B dans le scope de la fonction f2(), la variable B (définie hors du scope de f2()) n'est donc pas affectée.
  9. Même remarque que pour le 8.
  10. B est déclarée comme globale dans le scope de f4(), c'est donc B qui est impactée.
  11. Dans les fonctions, les passages de listes en argument se font par référence. Et donc toute modification de la liste à l'intérieur de la fonction aura un impact sur la liste à l'extérieur.
  12. L1 est une liste d'entier. Il n'y a pas d'alias.
  13. L2 est une liste de liste avec des alias : l'usage de l'opérateur * à créé trois alias: L2[0], L2[1] et L2[2].
  14. La fonction list() à créé une copie superficielle de la liste référencée par L3. Une modification (au premier niveau) de la liste à partir de B n'a pas d'impact sur L3.
  15. B[2] est une liste et la fonction list() n'a pas effectué une réelle copie de L3[2] : B[2] est un alias de L3[2].
  16. Même remarque que pour 15 : la fonction copy() ne fait qu'une copie superficielle.
  17. Même remarque que pour 15 : la fonction copy() ne fait qu'une copie superficielle.
  18. la fonction deepcopy() fait une copie profonde et donc une véritable copie de la liste.