web-dev-qa-db-fra.com

Python Plusieurs instructions d'affectation sur une seule ligne

(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?

30
mwcvitkovic

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. Dans foo = bar = 5, il y en a deux (target_list "=") productions et expression_list une partie est juste 5

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
18
mwcvitkovic

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.

12
John Coleman

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.

3
Patrick Maupin

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.

3
Veedrac