Por que em Python “0, 0 == (0, 0)” é igual a “(0, False)”?

118

Em Python (verifiquei apenas com Python 3.6, mas acredito que deve valer para muitas das versões anteriores também):

(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

Mas:

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

Por que o resultado difere entre as duas abordagens? O operador de igualdade lida com tuplas de maneira diferente?

Piotr Zakrzewski
fonte

Respostas:

156

As duas primeiras expressões são analisadas como tuplas:

  1. (0, 0) == 0(que é False), seguido por0
  2. 0, seguido por 0 == (0, 0)(que ainda é assim False).

As expressões são divididas dessa forma por causa da precedência relativa do separador de vírgula em comparação com o operador de igualdade: Python vê uma tupla contendo duas expressões, uma das quais passa a ser um teste de igualdade, em vez de um teste de igualdade entre duas tuplas.

Mas, em seu segundo conjunto de instruções, a = 0, 0 não pode ser uma tupla. Uma tupla é uma coleção de valores e, ao contrário de um teste de igualdade, a atribuição não tem valor em Python. Uma atribuição não é uma expressão, mas uma declaração; ele não tem um valor que possa ser incluído em uma tupla ou qualquer outra expressão circundante. Se você tentar algo como (a = 0), 0forçar a interpretação como uma tupla, receberá um erro de sintaxe. Isso deixa a atribuição de uma tupla a uma variável - o que poderia ser tornado mais explícito escrevendo-a a = (0, 0)- como a única interpretação válida de a = 0, 0.

Portanto, mesmo sem os parênteses na atribuição de a, tanto a ele quanto bo valor é atribuído (0,0), portanto a == bé True.

Mark Reed
fonte
17
Eu diria que o operador vírgula tem precedência menor do que igualdade, uma vez que a avaliação da igualdade precede a do operador vírgula: a igualdade tem precedência maior do que o operador vírgula. Mas isso é sempre uma fonte de confusão; só queria apontar que outras fontes podem inverter as coisas.
tomsmeding em
2
Você pode evitar a confusão de verbosidade inferior / superior dizendo, em vez disso, que ,vincula menos fortemente do que ==.
amalloy 01 de
4
A vírgula não é um operador docs.python.org/3.4/faq/…
Chris_Rands
48
Os documentos podem reivindicar tudo o que quiserem, mas não importa. Você pode escrever um analisador de modo que cada operador tenha sua própria produção e não haja "precedência" explícita em qualquer lugar da implementação, mas isso não impede que essas unidades sintáticas sejam operadores. Você pode redefinir "operador" de alguma forma específica de implementação , que é aparentemente o que eles faziam em Python, mas isso não muda a implicação do termo. A vírgula é efetivamente um operador que produz tuplas. Sua operatorness mostra, por exemplo, a maneira como sua precedência relativa é afetada por parênteses.
Mark Reed
68

O que você vê em todas as 3 instâncias é uma consequência da especificação gramatical da linguagem e como os tokens encontrados no código-fonte são analisados ​​para gerar a árvore de análise.

Dar uma olhada neste código de baixo nível deve ajudá-lo a entender o que acontece nos bastidores. Podemos pegar essas instruções python, convertê-las em código de bytes e, em seguida, descompilá-las usando o 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)é primeiro comparado com 0primeiro e avaliado para False. Uma tupla é então construída com este resultado e por último 0, então você obtém (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

Uma tupla é construída com 0o primeiro elemento. Para o segundo elemento, a mesma verificação é feita como no primeiro caso e avaliada para False, então você obtém (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

Aqui, como você pode ver, você está apenas comparando essas duas (0, 0)tuplas e retornando True.

cs95
fonte
20

Outra maneira de explicar o problema: você provavelmente está familiarizado com literais de dicionário

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

e literais de matriz

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

e literais de tupla

( 1, 2, 3 )

mas o que você não percebe é que, ao contrário dos literais de dicionário e array, os parênteses que você geralmente vê ao redor de um literal de tupla não fazem parte da sintaxe literal . A sintaxe literal para tuplas é apenas uma sequência de expressões separadas por vírgulas:

1, 2, 3

(uma "exprlist" na linguagem da gramática formal para Python ).

Agora, o que você espera do literal de array

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

avaliar para? Isso provavelmente parece muito mais que deveria ser o mesmo que

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

que, claro, avalia para [0, False]. Da mesma forma, com um literal de tupla explicitamente entre parênteses

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

não é surpreendente conseguir (0, False). Mas os parênteses são opcionais;

0, 0 == (0, 0)

É a mesma coisa. E é por isso que você consegue (0, False).


Se você está se perguntando por que os parênteses em torno de um literal de tupla são opcionais, é principalmente porque seria irritante ter que escrever atribuições de desestruturação dessa maneira:

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

Adicionar alguns parênteses em torno da ordem em que as ações são realizadas pode ajudá-lo a compreender melhor os 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

A vírgula é usada para separar expressões (usando parênteses podemos forçar comportamentos diferentes, é claro). Ao visualizar os snippets que você listou, a vírgula ,os separará e definirá quais expressões serão avaliadas:

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

A tupla (0, 0)também pode ser dividida de maneira semelhante. A vírgula separa duas expressões compostas de literais 0.

Dimitris Fasarakis Hilliard
fonte
6

No primeiro Python está fazendo uma tupla de duas coisas:

  1. A expressão (0, 0) == 0, que avalia paraFalse
  2. A constante 0

No segundo, é o contrário.

kindall
fonte
0

veja este exemplo:

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)

então o resultado:

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

então, a comparação apenas faz com o primeiro número (0 e r) no exemplo.

Emad Saeidi
fonte