Pourquoi en Python «0, 0 == (0, 0)» est-il égal à «(0, False)»?

118

En Python (j'ai vérifié uniquement avec Python 3.6 mais je pense que cela devrait également s'appliquer à de nombreuses versions précédentes):

(0, 0) == 0, 0   # results in a two element tuple: (False, 0)
0, 0 == (0, 0)   # results in a two element tuple: (0, False)
(0, 0) == (0, 0) # results in a boolean True

Mais:

a = 0, 0
b = (0, 0)
a == b # results in a boolean True

Pourquoi le résultat diffère-t-il entre les deux approches? L'opérateur d'égalité gère-t-il les tuples différemment?

Piotr Zakrzewski
la source

Réponses:

156

Les deux premières expressions sont toutes deux analysées comme des tuples:

  1. (0, 0) == 0(qui est False), suivi de0
  2. 0, suivi de 0 == (0, 0)(ce qui est toujours le Falsecas).

Les expressions sont divisées de cette façon en raison de la priorité relative du séparateur virgule par rapport à l'opérateur d'égalité: Python voit un tuple contenant deux expressions, dont l'une se trouve être un test d'égalité, au lieu d'un test d'égalité entre deux tuples.

Mais dans votre deuxième ensemble d'instructions, a = 0, 0 ne peut pas être un tuple. Un tuple est une collection de valeurs et, contrairement à un test d'égalité, l'affectation n'a aucune valeur en Python. Une affectation n'est pas une expression, mais une déclaration; il n'a pas de valeur pouvant être incluse dans un tuple ou dans toute autre expression environnante. Si vous essayez quelque chose comme (a = 0), 0pour forcer l'interprétation en tant que tuple, vous obtiendrez une erreur de syntaxe. Cela laisse l'attribution d'un tuple à une variable - qui pourrait être rendue plus explicite en l'écrivant a = (0, 0)- comme la seule interprétation valide de a = 0, 0.

Donc, même sans les parenthèses sur l'affectation à a, à la fois et à bobtenir la valeur attribuée (0,0), il en a == best de même True.

Mark Reed
la source
17
Je dirais que l'opérateur virgule a une priorité inférieure à l'égalité, puisque l'évaluation de l'égalité précède celle de l'opérateur virgule: l'égalité a une priorité plus élevée que l'opérateur virgule. Mais c'est toujours une source de confusion; voulait juste souligner que d'autres sources pourraient inverser les choses.
tomsmeding le
2
Vous pouvez éviter la confusion verbiage inférieur / supérieur en disant à la place que ,se lie moins étroitement que ==.
amalloy
4
La virgule n'est pas un opérateur docs.python.org/3.4/faq/…
Chris_Rands
48
Les documents peuvent prétendre que tout ce qu'ils veulent, mais cela n'a pas d'importance. Vous pouvez écrire un analyseur pour que chaque opérateur obtienne sa propre production et qu'il n'y ait pas de «précédence» explicite dans l'implémentation, mais cela n'empêche pas ces unités syntaxiques d'être des opérateurs. Vous pouvez redéfinir «opérateur» d'une manière spécifique à l'implémentation , ce qui est apparemment ce qu'ils ont fait en Python, mais cela ne change pas l'implication du terme. La virgule est en fait un opérateur qui produit des tuples. Son fonctionnement montre, par exemple, la façon dont sa priorité relative est affectée par les parenthèses.
Mark Reed
68

Ce que vous voyez dans les 3 instances est une conséquence de la spécification grammaticale du langage et de la façon dont les jetons rencontrés dans le code source sont analysés pour générer l'arbre d'analyse.

Jetez un œil à ce code de bas niveau devrait vous aider à comprendre ce qui se passe sous le capot. Nous pouvons prendre ces instructions python, les convertir en code octet puis les décompiler à l'aide du dismodule:

Cas 1: (0, 0) == 0, 0

>>> dis.dis(compile("(0, 0) == 0, 0", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               0 (0)
              6 COMPARE_OP               2 (==)
              9 LOAD_CONST               0 (0)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

(0, 0)est d'abord comparé au 0premier et évalué à False. Un tuple est ensuite construit avec ce résultat et le dernier 0, donc vous obtenez (False, 0).

Cas 2: 0, 0 == (0, 0)

>>> dis.dis(compile("0, 0 == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST               0 (0)
              6 LOAD_CONST               2 ((0, 0))
              9 COMPARE_OP               2 (==)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

Un tuple est construit avec 0comme premier élément. Pour le deuxième élément, la même vérification est effectuée que dans le premier cas et évaluée à False, donc vous obtenez (0, False).

Cas 3: (0, 0) == (0, 0)

>>> dis.dis(compile("(0, 0) == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               3 ((0, 0))
              6 COMPARE_OP               2 (==)
              9 POP_TOP
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

Ici, comme vous le voyez, vous comparez simplement ces deux (0, 0)tuples et revenez True.

cs95
la source
20

Une autre façon d'expliquer le problème: vous êtes probablement familier avec les littéraux de dictionnaire

{ "a": 1, "b": 2, "c": 3 }

et littéraux de tableau

[ "a", "b", "c" ]

et littéraux tuple

( 1, 2, 3 )

mais ce que vous ne réalisez pas, c'est que, contrairement aux littéraux de dictionnaire et de tableau, les parenthèses que vous voyez habituellement autour d'un littéral de tuple ne font pas partie de la syntaxe littérale . La syntaxe littérale des tuples est juste une séquence d'expressions séparées par des virgules:

1, 2, 3

(une "exprlist" dans le langage de la grammaire formelle pour Python ).

Maintenant, qu'attendez-vous du littéral du tableau

[ 0, 0 == (0, 0) ]

évaluer? Cela ressemble probablement beaucoup plus à ce que cela devrait être le même que

[ 0, (0 == (0, 0)) ]

ce qui évalue bien sûr à [0, False]. De même, avec un littéral de tuple explicitement entre parenthèses

( 0, 0 == (0, 0) )

ce n'est pas surprenant de l'obtenir (0, False). Mais les parenthèses sont facultatives;

0, 0 == (0, 0)

Est la même chose. Et c'est pourquoi vous obtenez (0, False).


Si vous vous demandez pourquoi les parenthèses autour d'un littéral de tuple sont facultatives, c'est en grande partie parce qu'il serait ennuyeux d'avoir à écrire des affectations de déstructuration de cette façon:

(a, b) = (c, d) # meh
a, b = c, d     # better
zwol
la source
17

L'ajout de quelques parenthèses autour de l'ordre dans lequel les actions sont effectuées peut vous aider à mieux comprendre les résultats:

# Build two element tuple comprising of 
# (0, 0) == 0 result and 0
>>> ((0, 0) == 0), 0
(False, 0)

# Build two element tuple comprising of
# 0 and result of (0, 0) == 0 
>>> 0, (0 == (0, 0))
(0, False)

# Create two tuples with elements (0, 0) 
# and compare them
>>> (0, 0) == (0, 0) 
True

La virgule est utilisée pour séparer les expressions (en utilisant des parenthèses, nous pouvons forcer un comportement différent, bien sûr). Lors de l'affichage des extraits de code que vous avez répertoriés, la virgule ,les séparera et définira les expressions qui seront évaluées:

(0, 0) == 0 ,   0
#-----------|------
  expr 1      expr2

Le tuple (0, 0)peut également être décomposé de la même manière. La virgule sépare deux expressions comprenant des littéraux 0.

Dimitris Fasarakis Hilliard
la source
6

Dans le premier, Python crée un tuple de deux choses:

  1. L'expression (0, 0) == 0, qui évalueFalse
  2. La constante 0

Dans le second, c'est l'inverse.

kindall
la source
0

regardez cet exemple:

r = [1,0,1,0,1,1,0,0,0,1]
print(r==0,0,r,1,0)
print(r==r,0,1,0,1,0)

puis résultat:

False 0 [1, 0, 1, 0, 1, 1, 0, 0, 0, 1] 1 0
True 0 1 0 1 0

alors la comparaison ne concerne que le premier nombre (0 et r) de l'exemple.

Emad Saeidi
la source