(Ne vous inquiétez pas, ce n'est pas une autre question concernant le déballage des tuples.)
En python, une instruction comme foo = bar = baz = 5
affecte les variables foo, bar et baz à 5. Il affecte ces variables de gauche à droite, comme le prouvent des exemples plus méchants comme
>>> foo[0] = foo = [0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
>>> foo = foo[0] = [0]
>>> foo
[[...]]
>>> foo[0]
[[...]]
>>> foo is foo[0]
True
Mais le référence du langage python indique que les instructions d'affectation ont la forme
(target_list "=")+ (expression_list | yield_expression)
et en affectation le expression_list
est évalué en premier, puis l'affectation se produit.
Alors, comment la ligne foo = bar = 5
être valide, étant donné que bar = 5
n'est pas un expression_list
? Comment ces affectations multiples sur une même ligne sont-elles analysées et évaluées? Suis-je en train de lire la référence de langue incorrecte?
Tout le mérite revient à @MarkDickinson, qui a répondu à cela dans un commentaire:
Remarquez le
+
dans(target_list "=")+
, ce qui signifie une ou plusieurs copies. Dansfoo = bar = 5
, il y en a deux(target_list "=")
productions etexpression_list
une partie est juste5
Tout target_list
productions (c'est-à-dire des choses qui ressemblent à foo =
) dans une instruction d'affectation, affectez, de gauche à droite, au expression_list
à l'extrémité droite de l'instruction, après le expression_list
est évalué.
Et bien sûr, la syntaxe d'affectation habituelle de `` décompression de tuple '' fonctionne dans cette syntaxe, vous permettant de faire des choses comme
>>> foo, boo, moo = boo[0], moo[0], foo[0] = moo[0], foo[0], boo[0] = [0], [0], [0]
>>> foo
[[[[...]]]]
>>> foo[0] is boo
True
>>> foo[0][0] is moo
True
>>> foo[0][0][0] is foo
True
Mark Dickinson a expliqué la syntaxe de ce qui se passe, mais les exemples étranges impliquant foo
montrent que la sémantique peut être contre-intuitive.
En C, =
Est un opérateur associatif à droite qui retourne comme valeur le RHS de l'affectation donc quand vous écrivez x = y = 5
, y=5
Est d'abord évalué (affectant 5 à y
dans le processus) et cette valeur (5) est ensuite affectée à x
.
Avant de lire cette question, j'ai supposé naïvement que la même chose se produit à peu près en Python. Mais, dans Python =
n'est pas une expression (par exemple, 2 + (x = 5)
est une erreur de syntaxe.) Donc Python doit réaliser plusieurs affectations d'une autre manière.
On peut démonter plutôt que deviner:
>>> import dis
>>> dis.dis('x = y = 5')
1 0 LOAD_CONST 0 (5)
3 DUP_TOP
4 STORE_NAME 0 (x)
7 STORE_NAME 1 (y)
10 LOAD_CONST 1 (None)
13 RETURN_VALUE
Voir this pour une description des instructions du code d'octet.
La première instruction pousse 5 sur la pile.
La deuxième instruction le duplique - alors maintenant le haut de la pile a deux 5
STORE_NAME(name)
"Implémente nom = TOS" selon la documentation du code d'octet
Ainsi, STORE_Name(x)
implémente x = 5
(Le 5 en haut de la pile), en faisant sauter ce 5 de la pile au fur et à mesure, après quoi STORE_Name(y)
implémente y = 5
avec les 5 autres sur la pile.
Le reste du bytecode n'est pas directement pertinent ici.
Dans le cas de foo = foo[0] = [0]
Le code d'octet est plus compliqué à cause des listes mais a une structure fondamentalement similaire. L'observation clé est qu'une fois que la liste [0]
Est créée et placée sur la pile, l'instruction DUP_TOP
N'en place pas une autre copie de [0]
sur la pile, à la place, il place une autre référence dans la liste. En d'autres termes, à ce stade, les deux premiers éléments de la pile sont des alias pour la même liste. Cela peut être vu plus clairement dans le cas un peu plus simple:
>>> x = y = [0]
>>> x[0] = 5
>>> y[0]
5
Lorsque foo = foo[0] = [0]
Est exécuté, la liste [0]
Est d'abord affectée à foo
puis un alias de la même liste est affecté à foo[0]
. C'est pourquoi il en résulte que foo
est une référence circulaire.
bar = 5
n'est pas une expression. L'affectation multiple est une instruction distincte de l'instruction d'affectation; l'expression est tout à droite de l'extrême droite =
.
Une bonne façon d'y penser est que le plus à droite =
est le principal séparateur; tout à droite se produit de gauche à droite, et tout à gauche se produit également de gauche à droite.
https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment_stmt
Une instruction d'affectation évalue la liste d'expressions (rappelez-vous qu'il peut s'agir d'une seule expression ou d'une liste séparée par des virgules, cette dernière produisant un tuple) et attribue l'objet résultant unique à chacune des listes cibles, de gauche à droite.