Noms et objets non immuables
Nous utilisons ici http://pythontutor.com pour visualiser pas à pas le déroulement d’un programme python et l’évolution des variables.
Un nom est une référence, pas une variable …
… la variable est l’objet référencé ; …
… et “
=
” ne fait donc pas de copie de variable.
En Python, il est plus juste de dire que “=
” relie un nom à un objet.
Malgré tout ça
on ne se gênera pas pour parler de variables
on se contentera d’être pragmatique
la surprise est de courte durée
Deux noms peuvent désigner la même chose…
[1]:
a = [5]
b = a
b[0] = 3
print(f"{a = }\n{b = }")
a = [3]
b = [3]
[2]:
from IPython.display import IFrame
IFrame("https://pythontutor.com/iframe-embed.html#code=a+%3D+%5B5%5D%0Ab+%3D+a%0Ab%5B0%5D+%3D+3&origin=opt-frontend.js&cumulative=false&heapPrimitives=true&drawParentPointers=false&textReferences=false&showOnlyOutputs=false&py=3&rawInputLstJSON=%5B%5D&curInstr=0&codeDivWidth=350&codeDivHeight=400",
width='100%', height=300)
[2]:
… mais pas toujours
[3]:
# Deux noms PEUVENT désigner la même chose
# mais pas pour toujours
i = 5
j = i
j = 3
print(f"{i = }\n{j = }")
i = 5
j = 3
[4]:
from IPython.display import IFrame
IFrame("https://pythontutor.com/iframe-embed.html#code=i+%3D+5%0Aj+%3D+i%0Aj+%3D+3&origin=opt-frontend.js&cumulative=false&heapPrimitives=true&drawParentPointers=false&textReferences=false&showOnlyOutputs=false&py=3&rawInputLstJSON=%5B%5D&curInstr=0&codeDivWidth=350&codeDivHeight=400",
width='100%', height=300)
[4]:
Attention aux arguments de type “list”
[5]:
# Fonction test
def somme(x) :
"Somme x avec lui-même"
x += x
return x
[6]:
# Test sur un entier
a = 2
print('avant exécution : ', f"{a = }")
b = somme(a)
print('après exécution : ', f"{a = }")
print('après exécution : ', f"{b = }")
avant exécution : a = 2
après exécution : a = 2
après exécution : b = 4
[7]:
# Test sur une liste
a = [1, 2]
print('avant exécution : ', f"{a = }")
b = somme(a)
print('après exécution : ', f"{a = }")
print('après exécution : ', f"{b = }")
avant exécution : a = [1, 2]
après exécution : a = [1, 2, 1, 2]
après exécution : b = [1, 2, 1, 2]
[8]:
# Mais que se passe-t-il?
from IPython.display import IFrame
IFrame("https://pythontutor.com/iframe-embed.html#code=def+somme(x)+%3A%0A++++%23+On+somme+x+avec+lui-m%C3%AAme%0A++++x+%2B%3D+x%0A++++return+x%0A%0A%23+entier%0Aa+%3D+2%0Ab+%3D+somme(a)%0A%0A%23+liste%0Aa+%3D+%5B1,+2%5D%0Ab+%3D+somme(a)&origin=opt-frontend.js&cumulative=false&heapPrimitives=true&drawParentPointers=false&textReferences=false&showOnlyOutputs=false&py=3&rawInputLstJSON=%5B%5D&curInstr=0&codeDivWidth=350&codeDivHeight=400",
width='100%', height=500)
[8]:
Portée des variables
La résolution d’un nom suit la règle dite LEGB (Local > Enclosed > Global > Built-in).
Assigner un nom localement (i.e. au sein d’une fonction) ne modifie pas le niveau global.
Le mot clef ``global`` permet de modifier cette règle.
[9]:
# pas de danger à utiliser le même nom localement et globalement
X = 'toto'
def f():
X = 42
print(f"local : {X = }")
f()
print(f"global: {X = }")
local : X = 42
global: X = 'toto'
[10]:
# Un cas fréquent (et potentiellement dangereux) est l'utilisation d'une variable globale dans une fonction
X = 3
def triple(a):
return a*X
triple(3)
[10]:
9
[11]:
# Catastrophe : la fonction a changé de valeur retournée.
X = 4
triple(3)
[11]:
12
conclusion : il faut assumer ses responsabilités quand on modifie un objet.
remarque : si le nom est une référence, il n’y a pas de pointeur natif en python.
Exercices
Explorer la liste liste_de_liste
construite par :
x = [1, 2, 3]
y = [x, x]
liste_de_liste = [y, y]
Que se passe-t-il quand on modifie un élément de x ? de y ?
Plus de fun ! Construire une liste récursive (dont un élément est elle-même ou bien la contient).