61 votes

Comment fonctionnent les affectations enchaînées ?

Une citation de quelque chose :

>>> x = y = somefunction()

est la même chose que

>>> y = somefunction()
>>> x = y

Question : Est-ce que

x = y = somefunction()

la même chose que

x = somefunction()
y = somefunction()

?

D'après ce que j'ai compris, ils devraient être identiques car somefunction ne peut retourner qu'une seule valeur.

6 votes

Vous pouvez utiliser l'option python au lieu de python-3.x puisqu'il est plus largement suivi et que votre question n'est pas spécifique à Python 3. Vous n'avez pas non plus besoin de répéter la balise dans le titre, mais il est bon de mentionner votre version de Python quelque part.

93voto

BobStein-VisiBone Points 398

Gauche d'abord

x = y = some_function()

est équivalent à

temp = some_function()
x = temp
y = temp

Notez l'ordre. La cible la plus à gauche est assignée en premier . (Une expression similaire en C mai affecter dans le ordre inverse .) D'après la documentation sur Mission Python :

...attribue l'unique objet résultant à chacune des listes de cibles, de gauche à droite.

Le démontage le montre :

>>> def chained_assignment():
...     x = y = some_function()
...
>>> import dis
>>> dis.dis(chained_assignment)
  2           0 LOAD_GLOBAL              0 (some_function)
              3 CALL_FUNCTION            0
              6 DUP_TOP
              7 STORE_FAST               0 (x)
             10 STORE_FAST               1 (y)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

ATTENTION : le même objet est toujours attribué à chaque cible. Ainsi, comme le soulignent @Wilduck et @andronikus, vous ne voudrez probablement jamais utiliser cette fonction :

x = y = []   # Wrong.

Dans le cas ci-dessus, x et y font référence à la même liste. Parce que les listes sont mutable en ajoutant à x semblent affecter y.

x = []   # Right.
y = []

Vous avez maintenant deux noms qui font référence à deux listes vides distinctes.

51voto

Wilduck Points 5116

Ils ne fonctionneront pas nécessairement de la même manière si somefunction renvoie une valeur mutable. Pensez-y :

>>> def somefunction():
...     return []
... 
>>> x = y = somefunction()
>>> x.append(4)
>>> x
[4]
>>> y
[4]
>>> x = somefunction(); y = somefunction()
>>> x.append(3)
>>> x
[3]
>>> y
[]

2 votes

Bonne prise. Et même si c'est une valeur immuable, x is y seront différentes (sauf les pièges comme la mise en cache des chaînes de caractères et des petits entiers).

7 votes

Oui, excellent point. J'ai eu des problèmes plus d'une fois quand j'ai voulu être paresseux et faire x = y = [] . Si x y y sont censés être des listes différentes, alors c'est l'heure des insectes.

0 votes

Je pense que x=y=somefunction fonctionne en assignant d'abord une valeur à y, puis en assignant x comme pointeur à y.

16voto

Greg Hewgill Points 356191

Et si somefunction() renvoie des valeurs différentes à chaque fois qu'il est appelé ?

import random

x = random.random()
y = random.random()

1 votes

C'est un contre-exemple alternatif valide bien que je n'aie pas fait une telle hypothèse dans le PO.

8 votes

Le fait est que vous devriez faire l'hypothèse inverse, à savoir que la fonction renvoie toujours la même chose, pour que ce que vous dites soit valable.

9voto

MariusSiuram Points 1497

Il en résulterait le même sólo si la fonction n'a pas d'effets secondaires et renvoie un singleton de manière déterministe (compte tenu de ses entrées).

Par exemple :

def is_computer_on():
    return True

x = y = is_computer_on()

ou

def get_that_constant():
    return some_immutable_global_constant

Notez que le résultat serait le même, mais que le processus pour y parvenir ne le serait pas :

def slow_is_computer_on():
    sleep(10)
    return True

Le contenu des variables x et y serait le même, mais l'instruction x = y = slow_is_computer_on() durerait 10 secondes, et son homologue x = slow_is_computer_on() ; y = slow_is_computer_on() durerait 20 secondes.

Ce serait presque la même chose si la fonction n'a pas d'effets secondaires et renvoie un immuable de manière déterministe (compte tenu de ses entrées).

Par exemple :

def count_three(i):
    return (i+1, i+2, i+3)

x = y = count_three(42)

Notez que les mêmes captures expliquées dans la section précédente s'appliquent.

Pourquoi je dis presque ? A cause de ça :

x = y = count_three(42)
x is y  # <- is True

x = count_three(42)
y = count_three(42)
x is y  # <- is False

Ok, en utilisant is est quelque chose d'étrange, mais cela illustre que le retour n'est pas le même. Ceci est important pour le cas mutable :

C'est dangereux et peut conduire à des bogues si la fonction retourne un objet mutable.

Cette question a également reçu une réponse. Dans un souci d'exhaustivité, je rejoue l'argument :

def mutable_count_three(i):
    return [i+1, i+2, i+3]

x = y = mutable_count_three(i)

Parce que dans ce scénario x y y sont le même objet, faire une opération comme x.append(42) ce qui signifie que les deux x y y contient une référence à une liste qui a maintenant 4 éléments.

Il n'en serait pas de même si la fonction avait des effets secondaires.

Considérer l'impression comme un effet secondaire (ce que je trouve valable, mais d'autres exemples peuvent être utilisés à la place) :

def is_computer_on_with_side_effect():
    print "Hello world, I have been called!"
    return True

x = y = is_computer_on_with_side_effect()  # One print

# The following are *two* prints:
x = is_computer_on_with_side_effect()
y = is_computer_on_with_side_effect()

Au lieu d'une impression, il peut s'agir d'un effet secondaire plus complexe ou plus subtil, mais le fait demeure : la méthode est appelée une ou deux fois et cela peut entraîner un comportement différent.

Il n'en serait pas de même si la fonction est non déterministe compte tenu de ses entrées

Peut-être une simple méthode aléatoire :

def throw_dice():
    # This is a 2d6 throw:
    return random.randint(1,6) + random.randint(1,6)

x = y = throw_dice()  # x and y will have the same value

# The following may lead to different values:
x = throw_dice()
y = throw_dice()

Mais les choses liées à l'horloge, aux compteurs globaux, au système, etc. sont susceptibles d'être non déterministes étant donné l'entrée, et dans ces cas, la valeur de x y y peuvent diverger.

6voto

user2560053 Points 141

Comme l'a déjà indiqué Bob Stein, l'ordre d'affectation est important ; voyez le cas suivant, très intéressant :

L = L[1] = [42, None]

Maintenant, qu'est-ce qui contient L ? Vous devez comprendre qu'un objet unique étant initialement [42, None] qui est affecté à L enfin, quelque chose comme L[1] = L est effectuée. Vous avez ainsi créé une "liste" cyclique infinie (le mot "liste" étant ici plus proche d'un quelconque CONS en Lisp avec un scalaire 42 étant le CAR et la liste elle-même étant le CDR ).

Tapez juste :

>>> L
[42, [...]]

puis amusez-vous en tapant L[1] entonces L[1][1] entonces L[1][1][1] jusqu'à ce que vous atteigniez la fin...

Conclusion

Cet exemple est plus compliqué à comprendre que d'autres dans d'autres réponses, mais d'un autre côté, vous pouvez voir beaucoup plus rapidement que

L = L[1] = [42, None]

n'est pas la même chose que

L[1] = L = [42, None]

parce que le second lèvera une exception si L n'est pas défini précédemment alors que le premier fonctionnera toujours.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X