Perché in Python "0, 0 == (0, 0)" è uguale a "(0, False)"?


118

In Python (ho controllato solo con Python 3.6 ma credo che dovrebbe valere anche per molte delle versioni precedenti):

(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

Ma:

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

Perché il risultato differisce tra i due approcci? L'operatore di uguaglianza gestisce le tuple in modo diverso?

Risposte:


156

Le prime due espressioni vengono analizzate entrambe come tuple:

  1. (0, 0) == 0(che è False), seguito da0
  2. 0, seguito da 0 == (0, 0)(che è ancora Falsecosì).

Le espressioni sono suddivise in questo modo a causa della precedenza relativa del separatore virgola rispetto all'operatore di uguaglianza: Python vede una tupla contenente due espressioni, una delle quali è un test di uguaglianza, invece di un test di uguaglianza tra due tuple.

Ma nella tua seconda serie di istruzioni, a = 0, 0 non può essere una tupla. Una tupla è una raccolta di valori e, a differenza di un test di uguaglianza, l'assegnazione non ha valore in Python. Un incarico non è un'espressione, ma un'affermazione; non ha un valore che può essere incluso in una tupla o in qualsiasi altra espressione circostante. Se provassi qualcosa di simile (a = 0), 0per forzare l'interpretazione come una tupla, otterrai un errore di sintassi. Ciò lascia l'assegnazione di una tupla a una variabile - che potrebbe essere resa più esplicita scrivendola a = (0, 0)- come unica interpretazione valida di a = 0, 0.

Quindi, anche senza le parentesi nell'assegnazione a a, sia esso che gli venga bassegnato il valore (0,0), quindi a == bè così True.


17
Direi che l'operatore virgola ha una precedenza inferiore rispetto all'uguaglianza, poiché la valutazione dell'uguaglianza precede quella dell'operatore virgola: l'uguaglianza ha precedenza maggiore dell'operatore virgola. Ma questo è sempre fonte di confusione; Volevo solo sottolineare che altre fonti potrebbero capovolgere le cose.
tomsmeding

2
Puoi evitare la confusione verbale inferiore / superiore dicendo invece che ,lega meno strettamente di ==.
amalloy

4
La virgola non è un operatore docs.python.org/3.4/faq/…
Chris_Rands

48
I documenti possono affermare che tutto ciò che vogliono, ma non importa. Puoi scrivere un parser in modo che ogni operatore abbia la propria produzione e non ci sia alcuna "precedenza" esplicita in nessun punto dell'implementazione, ma ciò non impedisce a quelle unità sintattiche di essere operatori. Puoi ridefinire "operatore" in qualche modo specifico dell'implementazione , che è apparentemente ciò che hanno fatto in Python, ma ciò non cambia l'implicazione del termine. La virgola è effettivamente un operatore che produce tuple. La sua operatività mostra, ad esempio, il modo in cui la sua precedenza relativa è influenzata dalle parentesi.
Mark Reed

68

Ciò che vedi in tutti e 3 i casi è una conseguenza della specifica grammaticale del linguaggio e del modo in cui i token incontrati nel codice sorgente vengono analizzati per generare l'albero di analisi.

Dare un'occhiata a questo codice di basso livello dovrebbe aiutarti a capire cosa succede sotto il cofano. Possiamo prendere queste istruzioni python, convertirle in byte code e poi decompilarle usando il dismodulo:

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)viene prima confrontata con 0prima e valutata con False. Una tupla viene quindi costruita con questo risultato e dura 0, quindi ottieni (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 è costruita con 0 come primo elemento. Per il secondo elemento, viene eseguito lo stesso controllo del primo caso e valutato False, quindi ottieni (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

Qui, come vedi, stai solo confrontando quelle due (0, 0)tuple e tornando True.


20

Un altro modo per spiegare il problema: probabilmente hai familiarità con i letterali del dizionario

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

e letterali di matrice

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

e tuple letterali

( 1, 2, 3 )

ma quello che non ti rendi conto è che, a differenza dei dizionari e dei letterali di matrice, le parentesi che di solito vedi attorno a un letterale di tupla non fanno parte della sintassi letterale . La sintassi letterale per le tuple è solo una sequenza di espressioni separate da virgole:

1, 2, 3

(un "exprlist" nel linguaggio della grammatica formale per Python ).

Ora, cosa ti aspetti dall'array letterale

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

valutare a? Probabilmente sembra molto più simile a come dovrebbe essere

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

che ovviamente valuta a [0, False]. Allo stesso modo, con una tupla letterale esplicitamente tra parentesi

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

non è sorprendente ottenere (0, False). Ma le parentesi sono opzionali;

0, 0 == (0, 0)

è la stessa cosa. Ed è per questo che ottieni (0, False).


Se ti stai chiedendo perché le parentesi attorno a una tupla letterale sono opzionali, è in gran parte perché sarebbe fastidioso dover scrivere assegnazioni destrutturanti in questo modo:

(a, b) = (c, d) # meh
a, b = c, d     # better

17

L'aggiunta di un paio di parentesi attorno all'ordine in cui vengono eseguite le azioni potrebbe aiutarti a comprendere meglio i risultati:

# 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 virgola è usata per separare le espressioni (usando le parentesi possiamo forzare un comportamento diverso, ovviamente). Durante la visualizzazione degli snippet che hai elencato, la virgola ,lo separerà e definirà quali espressioni verranno valutate:

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

La tupla (0, 0)può anche essere scomposta in modo simile. La virgola separa due espressioni che comprendono i letterali 0.


6

Nel primo Python sta creando una tupla di due cose:

  1. L'espressione (0, 0) == 0, che restituisceFalse
  2. La costante 0

Nella seconda è il contrario.


0

guarda questo esempio:

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)

quindi risultato:

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

quindi il confronto si limita al primo numero (0 er) nell'esempio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.