Essayez de résoudre ces trois problèmes, puis vérifiez les réponses à la fin de l'article.
Conseil : les problèmes ont quelque chose en commun, alors revenez sur le premier problème lorsque vous passez au deuxième ou au troisième, ce sera plus facile pour vous.
Première tâche
Il existe plusieurs variables:
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
Qu'est-ce qui sera affiché lors de l'impression
l
et s
?
Deuxième tâche
Définissez une fonction simple:
def f(x, s=set()):
s.add(x)
print(s)
Que se passe-t-il si vous appelez:
>>f(7)
>>f(6, {4, 5})
>>f(2)
Troisième tâche
Définissons deux fonctions simples:
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
Qu'obtient-on après avoir exécuté ces commandes?
>>f_inner = f()
>>print(f_inner(2))
>>g_inner = g()
>>print(g_inner(2))
Êtes-vous confiant dans vos réponses? Vérifions votre cas.
La solution au premier problème
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Pourquoi la deuxième liste réagit-elle à une modification de son premier élément
a.append(5)
, alors que la première liste ignore complètement la même modification x+=5
?
Solution du deuxième problème
Voyons ce qui se passe:
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Attendez, le dernier résultat ne devrait-il pas être
{2}
?
La solution au troisième problème
Le résultat sera comme ceci:
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Pourquoi
g_inner(2)
pas 3
? Pourquoi f()
la fonction interne se souvient de la portée externe, mais la fonction interne g()
ne se souvient pas? Ils sont presque identiques!
Explication
Et si je vous disais que tous ces comportements étranges ont à voir avec la différence entre les objets mutables et immuables en Python?
Les objets mutables tels que les listes, les ensembles ou les dictionnaires peuvent être modifiés sur place. Les objets immuables, tels que les valeurs numériques et de chaîne, les tuples, ne peuvent pas être modifiés; leur «changement» conduira à la création de nouveaux objets.
Explication de la première tâche
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Puisqu'elle est
x
immuable, l'opération x+=5
ne modifie pas l'objet d'origine, mais en crée un nouveau. Mais le premier élément de la liste se réfère toujours à l'objet d'origine, donc sa valeur ne change pas.
Car un objet mutable, la commande
a.append(5)
change l'objet d'origine (plutôt que d'en créer un nouveau), et la liste s
«voit» les changements.
Explication de la deuxième tâche
def f(x, s=set()):
s.add(x)
print(s)
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Tout est clair avec les deux premiers résultats: la première valeur est
7
ajoutée à l'ensemble initialement vide et il s'avère {7}
; puis la valeur est 6
ajoutée à l'ensemble {4, 5}
et il s'avère {4, 5, 6}
.
Et puis les bizarreries commencent. La valeur est
2
ajoutée non pas à l'ensemble vide, mais à {7}. Pourquoi? La valeur initiale du paramètre optionnel n'est s
évaluée qu'une seule fois: lors du premier appel, s sera initialisé comme un ensemble vide. Et comme il est mutable, f(7)
il sera changé en place après avoir été appelé . Le deuxième appel f(6, {4, 5})
n'affectera pas le paramètre par défaut: il est remplacé par un ensemble {4, 5}
, c'est-à-dire qu'il s'agit d' {4, 5}
une variable différente. Le troisième appel f(2)
utilise la même variables
qui a été utilisé lors du premier appel, mais il n'est pas réinitialisé en tant qu'ensemble vide, mais est à la place pris à partir de sa valeur précédente {7}
.
Par conséquent, vous ne devez pas utiliser d'arguments mutables comme arguments par défaut. Dans ce cas, la fonction doit être modifiée:
def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)
Explication de la troisième tâche
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Nous avons affaire ici à des fermetures: les fonctions internes se souviennent de ce à quoi ressemblait leur espace de noms externe quand elles ont été définies. Ou du moins ils devraient s'en souvenir, mais la deuxième fonction fait l'interface de poker et se comporte comme si elle n'avait pas entendu parler de son espace de noms externe.
Pourquoi cela arrive-t-il? Lorsque nous exécutons
l.append(x)
, l'objet mutable créé lorsque la fonction a été définie change. Mais la variable l
fait toujours référence à l'ancienne adresse en mémoire. Cependant, une tentative de modification d'une variable immuable dans la deuxième fonction y += x
conduit au fait que y commence à se référer à une autre adresse en mémoire: le y d'origine sera oublié, ce qui entraînera une erreur UnboundLocalError.
Conclusion
La différence entre les objets mutables et immuables en Python est très importante. Évitez le comportement étrange décrit dans cet article. Surtout:
- .
- - .