En Python, les variables sont crées automatiquement à leur première utilisation. Pour créer une variable, il suffit donc de l’utiliser en l’affectant une première fois, c’est à dire d’écrire nom_variable=valeur_de_la_variable. Une variable peut prendre n’importe quel nom, tant qu’elle respecte les règles suivantes :

  • Son nom commence de préférence par une lettre minuscule (a à z) ou majuscule (A à Z), ou bien par le caractère souligné (_)
  • Pour la suite de son nom, on peut utiliser les lettres minuscules et majuscule, le souligné et un chiffre (0 à 9)
  • Il faut éviter de préférence d’utiliser un mot réservé (commande Python)

Avant de commencer, validez la cellule suivante qui nous permettra d'accéder à un outil très pratique : Python tutor.

from metakernel import register_ipython_magics
register_ipython_magics()

Affectations - rappels

a=7 # Affectation simple
x=y=7 # Affectation multiple
z,t="toto",8 # Affectation en parallèle
print(a)
print(x,y)
print(z,t)

Variables locales - Variables globales

Variables locales

Une variable locale est une variable définie à l’intérieur d’une fonction. Elle n’est utilisable qu’à l’intérieur de celle-ci, le reste du programme ignorant totalement son existence.

Cet exemple illustre le mécanisme de variable locale et de passage de paramètre. La variable locale varLoc est utilisable à l’intérieur de la fonction maFonction et remplit son rôle.

def maFonction(param):
    varLoc=4
    varLoc=varLoc+param
    print(varLoc)

maFonction(5)

Néanmoins, cette variable n'a plus d'existance à l'extérieur de la fonction. Exécutez le code suivant et trouver dans le message d'erreur ce qui prouve que la variable locale est détruite en sortant de la fonction.

maFonction(3)
print(varLoc)

Variables globales

Il existe des cas ou on souhaite qu'une variable soit persistante tout au long de la vie du programme, quelque soit la fonction qui l'utilise. C'est ce qu'on appelle une variable globale.

Il faut être assez prudent dans l'utilisation de ces variables : une trop grande quantité de variables globales rend le programme difficile à lire et à maintenir. C'est une technique de programmation peu élégante.

On préfère en général avoir recours à des objets (voir le classeur sur la programmation orientée objet).

Une variable globale est définie dans le programme principal (souvent au début dans la section d’initialisa- tion). Elle est par défaut accessible en lecture seule dans l’ensemble des fonctions définies dans le programme :

varGlo=5
def maFonction1():
    print ("varGlo dans fonction1",varGlo)

def maFonction2():
    varGlo=4
    print ("varGlo dans fonction2",varGlo)

print("Debut programme principal",varGlo)
maFonction1()
maFonction2()
print("Fin programme principal",varGlo)

Afin de bien comprendre ce qui s'est passé, revoyons l'exécution de cette cellule au ralenti :

%%tutor
varGlo=5
def maFonction1():
    print ("varGlo dans fonction1",varGlo)

def maFonction2():
    varGlo=4
    print ("varGlo dans fonction2",varGlo)

print("Debut programme principal",varGlo)
maFonction1()
maFonction2()
print("Fin programme principal",varGlo)

Vous constaterez dans l'exemple ci-dessus que -varGlo est accessible en lecture dans maFonction1 -varGlo a pu être modifiée dans maFonction2 -les modifications de varGlo dans maFonction2 n'ont pas été répercutées dans le programme principal !

On peut avoir l'impression en lisant le code que varGlo prend la valeur 4 dès que maFonction2 est exécutée. En réalité, maFonction2 va créer une variable locale de même nom que varGlo qui n'aura d'existance qu'à l'intérieur de la fonction. La variable globale varGlo créée au début du programme, elle, est inchangée.

Voila pourquoi il faut être prudent quant à l'utilisation des variables globales.

Modifier une variable globale dans une fonction

Il est bien sûr possible de modifier le contenu d'une variable globale à l'intérieur d'une fonction. Pour ce faire, on utilisera la commande python global pour indiquer à la fonction les variables globales accessibles en modification.

Etudiez l'exemple suivant :

varGlo=5

def maFonction3():
    global varGlo
    varGlo=4

maFonction3()
## Que vaut varGlo après appel de maFonction3 ?
## indiquez votre réponse dans la variable réponse

reponse = ...
assert varGlo == reponse

Exercice

  • Créer une variable globale nbCoups initialisée à 0
  • Ecrire une fonction trouve
    • prenant en paramètre un entier n
    • créant une variable locale inc qui est un entier aléatoire entre 1 et 10
    • renvoyant True si $n$ est égal à inc, False sinon
    • le nombre de coups devra être incrémenté à chaque appel de la fonction

Combien de coups vous a t-il fallu pour tomber sur le nombre choisi pr l'ordinateur ?

from random import randint
# YOUR CODE HERE
raise NotImplementedError()
trouve(8)
nbCoups
nbCoups = 0
trouve(4)
assert nbCoups == 1

Les références partagées : le cas des listes

Les listes Python sont des références, c’est à dire que écrire a = [(1,0,0),(0,1,0),(0,0,1)] ne signifie pas que a vaut [(1,0,0),(0,1,0),(0,0,1)] mais que a référence la liste [(1,0,0),(0,1,0),(0,0,1)], c'est à dire contient un pointeur vers la zone mémoire qui contient cette liste.

La subtilité est que la commande b = a ne va pas duppliquer le contenu de la liste mais recopier une référence sur cette même liste. Si on modifie b, alors a sera également modifié.

L’examen des deux exemples suivant illustre ce concept.

a = [(1,0,0), (0,1,0), (0,0,1)]
print ("a=",a)

b = a # On copie la référence, pas la liste

b[0] = (1,1,0) # On modifie b
print("b=",b)

print("a=",a) # Ouuups !!!

Dans l'exemple ci-dessus, la modification de $b$ entraîne sans qu'on s'y attende la modification de $a$ !!

Ce n'est pas un bug de Python, c'est normal quand on sait comment Python gère la mémoire. Revoyons cette séquence au ralenti en validant la cellule suivante:

%%tutor
a = [(1,0,0), (0,1,0), (0,0,1)]
print ("a=",a)

b = a # On copie la référence, pas la liste

b[0] = (1,1,0) # On modifie b
print("b=",b)

print("a=",a) # Ouuups !!!

Vous le voyez, l'outil python tutor est très pratique pour observer dans le détail comment Python gère la mémoire. N'hésitez pas par la suite à l'utiliser en ajoutant %%tutor en première ligne de cellule.

Pour suivre l'utilisation de la mémoire, on peut aussi utiliser la commande id qui permet d'obtenir l'adresse mémoire de l'objet référencé par une variable :

print("adresse de a",id(a))
print("adresse de b",id(b))

Tout s'explique !! a et b son un seul et même objet !

Mais comment faire pour obtenir dans b une liste identique mais indépendante de a ?

a = [(1,0,0), (0,1,0),(0,0,1)]
b = [(1,0,0), (0,1,0),(0,0,1)]
print("adresse de a",id(a))
print("adresse de b",id(b))

ah ! les adresses de a et de b sont différentes. Si je modifie b, a ne devrait pas être impacté. Vérifions le de suite !

b[0] = (1,1,0) # On modifie b
print("b=",b)

print("a=",a) # Ouuuf !!!

On devra donc être prudent sur l’utilisation d’une affectation du type b=a surtout lorsqu’on travaille avec des listes ou des chaîne de caractères, des effets inattendus peuvent alors se produire.

# une erreur classique
liste_desordre=[3,2,5,6,3,1]
liste_ordre=liste_desordre
liste_ordre.sort()
print ("Liste en ordre",liste_ordre)
print ("Liste de départ",liste_desordre) # oups

Et voici comment réaliser une véritable dupplication de notre liste : on utilise la méthode copy()

liste_desordre=[3,2,5,6,3,1]
liste_ordre=liste_desordre.copy()
liste_ordre.sort()
print ("Liste en ordre",liste_ordre)
print ("Liste de départ",liste_desordre) # ouf

Exercice

Ecrire une fonction double1 qui prend une liste de nombre en paramètre et qui multiplie chaque élément par 2. La fonction double1 renvoie la liste doublée. La liste de départ est modifiée par cette fonction.

Ecrire une fonction double2 qui prend une liste de nombre en paramètre et renvoie une liste contenant les doubles de tous les éléments de la liste passée en paramètre. La liste de départ n'est pas modifiée par cette fonction.

Regardez la cellule de tests pour plus d'informations si nécessaire

# YOUR CODE HERE
raise NotImplementedError()
# Cellule de test

liste = [3,4,5]
assert double1(liste) ==[6,8,10]
# double1 modifie la liste de départ
assert liste == [6,8,10]

liste = [3,4,5]
assert double2(liste) ==[6,8,10]
# double2 ne modifie pas la liste de départ
assert liste == [3,4,5]

Le cas des chaînes

Lorsque l'on manipule des variables contenant des chaînes de caractères, manipule t-on les références vers ces chaînes ou bien les contenus des chaînes de caractères ?

Le comportement des chaînes de caractères est-il identique à celui des listes ou non ?

En vous inspirant des manipulations faites listes mais avec des chaînes de caractères, tentez d'apporter des réponses à ces questions.

# Faites vos tests ici

Lorsque on manipule des chaines de caractères, on manipule des références vers ces chaines. De ce point de vue, les chaînes se comporte comme les listes.

Observons si on peut avoir un effet de bord (modification non prévue d'une variable) :

chaine = 'salut tout le monde'
autrechaine = chaine
autrechaine[2]="a"

Il semble que les chaînes ne puissent pas être modifiées sur place. Ce sont des objets non mutables. Cela les distingue de listes. De ce point de vue, cela nous protège des effets de bord constatés sur les listes.

Oui mais qu'en est-il de la concaténation ? en effet, on peut ajouter une chaine au bout d'une chaine et en ce sens modifier notre chaine... regardons :

autrechaine += " est moche"
print(autrechaine)
print(chaine)

Tout va bien, autrechaine est modifiée mais sans effet non désiré sur chaine. Mais comment est-ce possible puisque on a modifié autrechaine ?

print(id(autrechaine))
print(id(chaine))

Après ajout d'une autre chaine au bout de la variable autrechaine, la variable autrechaine pointe sur un nouvel objet. Cela prouve que les chaines de caractères sont des objets non mutables contrairement aux listes.

Pas de crainte d'effet de bord sur les chaines.

Conclusion

Il y a deux grands types d'objets en python :

  • les objets mutables
  • les objets immuables

Les objets mutables sont des objets qui acceptent d'être modifiés sans avoir besoin d'être recopiés - on parle alors de modification sur place. Exemples : les listes, les dictionnaires, les ensembles.

Les objets immuables sont des objets qui nécessitent d'être recréés pour être modifiés. Exemple : les chaines de caractères ou les tuples.

Il faut être très prudent lors de la manipulation d'objets mutables à cause du problème des références partagées qui peut occasionner des effets de bords non désirés : modification non prévue d'autres variables que celle sur laquelle on agit.