¿Por qué en Python “0, 0 == (0, 0)” es igual a “(0, Falso)”?

118

En Python (verifiqué solo con Python 3.6, pero creo que también debería ser válido para muchas de las versiones anteriores):

(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

Pero:

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

¿Por qué el resultado difiere entre los dos enfoques? ¿El operador de igualdad maneja las tuplas de manera diferente?

Piotr Zakrzewski
fuente

Respuestas:

156

Las dos primeras expresiones se analizan como tuplas:

  1. (0, 0) == 0(que es False), seguido de0
  2. 0, seguido de 0 == (0, 0)(que sigue siendo Falseasí).

Las expresiones se dividen de esa manera debido a la precedencia relativa del separador de comas en comparación con el operador de igualdad: Python ve una tupla que contiene dos expresiones, una de las cuales es una prueba de igualdad, en lugar de una prueba de igualdad entre dos tuplas.

Pero en su segundo conjunto de declaraciones, a = 0, 0 no puede ser una tupla. Una tupla es una colección de valores y, a diferencia de una prueba de igualdad, la asignación no tiene valor en Python. Una asignación no es una expresión, sino una declaración; no tiene un valor que pueda incluirse en una tupla o cualquier otra expresión circundante. Si intenta algo como (a = 0), 0para forzar la interpretación como una tupla, obtendrá un error de sintaxis. Eso deja la asignación de una tupla a una variable, que podría hacerse más explícita escribiéndola a = (0, 0), como la única interpretación válida de a = 0, 0.

Entonces, incluso sin los paréntesis en la asignación a a, tanto a ella como a ella bse les asigna el valor (0,0), por a == blo tanto True.

Mark Reed
fuente
17
Yo diría que el operador de coma tiene menor precedencia que la igualdad, ya que la evaluación de la igualdad precede a la del operador de coma: la igualdad tiene mayor precedencia que el operador de coma. Pero esto es siempre una fuente de confusión; solo quería señalar que otras fuentes podrían cambiar las cosas.
tomsmeding
2
Puede evitar la confusión de verborrea más baja / más alta diciendo en cambio que se ,une con menos fuerza que ==.
amalloy
4
La coma no es un operador docs.python.org/3.4/faq/…
Chris_Rands
48
Los médicos pueden afirmar que todo lo que quieran, pero no importa. Puede escribir un analizador de modo que cada operador obtenga su propia producción y no haya "precedencia" explícita en ninguna parte de la implementación, pero eso no evita que esas unidades sintácticas sean operadores. Puede redefinir "operador" de alguna manera específica de implementación , que es aparentemente lo que hicieron en Python, pero eso no cambia la implicación del término. La coma es efectivamente un operador que produce tuplas. Su operabilidad se muestra, por ejemplo, en la forma en que los paréntesis afectan su precedencia relativa.
Mark Reed
68

Lo que ve en las 3 instancias es una consecuencia de la especificación gramatical del lenguaje y de cómo se analizan los tokens encontrados en el código fuente para generar el árbol de análisis.

Echar un vistazo a este código de bajo nivel debería ayudarlo a comprender lo que sucede debajo del capó. Podemos tomar estas declaraciones de Python, convertirlas en código de bytes y luego descompilarlas usando el dismódulo:

Caso 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)primero se compara con el 0primero y se evalúa como False. Luego se construye una tupla con este resultado y por último 0, por lo que se obtiene (False, 0).

Caso 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

Una tupla se construye con 0el primer elemento. Para el segundo elemento, se realiza la misma verificación que en el primer caso y se evalúa para Falseobtener (0, False).

Caso 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

Aquí, como puede ver, solo está comparando esas dos (0, 0)tuplas y regresando True.

cs95
fuente
20

Otra forma de explicar el problema: probablemente esté familiarizado con los literales del diccionario.

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

y literales de matriz

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

y tuplas literales

( 1, 2, 3 )

pero lo que no se da cuenta es que, a diferencia de los literales de diccionario y de matriz, los paréntesis que normalmente ve alrededor de una tupla literal no son parte de la sintaxis literal . La sintaxis literal de las tuplas es solo una secuencia de expresiones separadas por comas:

1, 2, 3

(un "exprlist" en el lenguaje de la gramática formal de Python ).

Ahora, ¿qué esperas de la matriz literal

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

evaluar a? Eso probablemente se parece mucho más a que debería ser lo mismo que

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

que por supuesto se evalúa [0, False]. Del mismo modo, con un literal de tupla explícitamente entre paréntesis

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

no es sorprendente conseguirlo (0, False). Pero los paréntesis son opcionales;

0, 0 == (0, 0)

es lo mismo. Y es por eso que lo consigues (0, False).


Si se pregunta por qué los paréntesis alrededor de un literal de tupla son opcionales, es en gran parte porque sería molesto tener que escribir asignaciones de desestructuración de esa manera:

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

Agregar un par de paréntesis alrededor del orden en que se realizan las acciones puede ayudarlo a comprender mejor los resultados:

# 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 coma se usa para separar expresiones (usando paréntesis podemos forzar comportamientos diferentes, por supuesto). Al ver los fragmentos que enumeró, la coma ,los separará y definirá qué expresiones se evaluarán:

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

La tupla (0, 0)también se puede desglosar de forma similar. La coma separa dos expresiones que comprenden los literales 0.

Dimitris Fasarakis Hilliard
fuente
6

En el primero, Python está haciendo una tupla de dos cosas:

  1. La expresión (0, 0) == 0, que se evalúa comoFalse
  2. El constante 0

En el segundo es al revés.

un poco
fuente
0

mira este ejemplo:

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)

entonces resultado:

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

luego, la comparación solo se aplica al primer número (0 y r) en el ejemplo.

Emad Saeidi
fuente