Perché l'espressione 0 <0 == 0 restituisce False in Python?


136

Guardando Queue.py in Python 2.6, ho trovato questo costrutto che ho trovato un po 'strano:

def full(self):
    """Return True if the queue is full, False otherwise
    (not reliable!)."""
    self.mutex.acquire()
    n = 0 < self.maxsize == self._qsize()
    self.mutex.release()
    return n

Se maxsizeè 0 la coda non è mai piena.

La mia domanda è: come funziona in questo caso? Come 0 < 0 == 0viene considerato falso?

>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True

0 <Vero è uguale a Falso in Python?
Marino Šimić,

3
@Marino Šimić: dal secondo esempio mostrato nella domanda del PO >>> (0) < (0 == 0), chiaramente non lo è.
martineau,

3
Uno dei motivi per cui non dovresti scrivere codice come n = 0 < self.maxsize == self._qsize()in primo luogo, in nessuna lingua. Se i tuoi occhi devono spostarsi avanti e indietro attraverso la linea più volte per capire cosa sta succedendo, non è una linea ben scritta. Basta dividerlo in più righe.
BlueRaja - Danny Pflughoeft,

2
@Blue: sono d'accordo con non scrivere un simile confronto in quel modo, ma dividerlo in righe separate è un po 'esagerato per due confronti. Spero tu voglia dire, dividerlo in confronti separati. ;)
Jeff Mercado,

2
@Blue: non l'ho scritto, è in Python 2.6. Stavo solo cercando di capire cosa stesse succedendo.
Marcelo Santos,

Risposte:


113

Credo che Python abbia una gestione dei casi speciali per le sequenze di operatori relazionali per rendere facili i confronti tra i range. È molto più bello poter dire 0 < x <= 5che dire (0 < x) and (x <= 5).

Questi sono chiamati confronti concatenati . E questo è un link alla documentazione per loro.

Con gli altri casi di cui parli, la parentesi forza un operatore relazionale a essere applicato prima dell'altro, e quindi non sono più confronti concatenati. E poiché Truee Falsehai valori come numeri interi, ottieni le risposte che ottieni dalle versioni tra parentesi.


è interessante provare alcuni di questi confronti e specificare int () e bool (). Mi sono reso conto che il valore bool () di qualsiasi diverso da zero è 1. Immagino che non avrei mai provato a specificare direttamente altro che bool (0) o bool (1) prima di questo esperimento
mentale

Volevi collegare a questa sezione invece? docs.python.org/2/reference/expressions.html#comparisons
tavnab

@tavnab - Sì. Proverò a ricordare di ripararlo. Vado anche a controllare la cronologia delle modifiche. Non sembra un errore che farei. 😕
Onnipotente il

42

Perché

(0 < 0) and (0 == 0)

lo è False. È possibile concatenare gli operatori di confronto e questi vengono automaticamente espansi nei confronti a coppie.


EDIT - chiarimenti su True e False in Python

In Python Truee Falsesono solo istanze di bool, che è una sottoclasse di int. In altre parole, Trueè davvero solo 1.

Il punto è che puoi usare il risultato di un confronto booleano esattamente come un numero intero. Questo porta a confondere cose come

>>> (1==1)+(1==1)
2
>>> (2<1)<1
True

Ma questi accadranno solo se parentesi i confronti in modo che vengano valutati per primi. Altrimenti Python espanderà gli operatori di confronto.


2
Ho visto un uso interessante per i valori booleani utilizzati come numeri interi ieri. L'espressione 'success' if result_code == 0 else 'failure'può essere riscritta poiché ('error', 'success')[result_code == 0]prima non avevo mai visto un booleano selezionare un elemento in un elenco / tupla.
Andrew Clark,

'bool' è stato aggiunto qualche volta attorno a Python 2.2.
MRAB,

18

Lo strano comportamento che stai vivendo deriva dalla capacità dei pitoni di concatenare le condizioni. Poiché trova 0 non è inferiore a 0, decide che l'intera espressione viene valutata come falsa. Non appena lo dividi in condizioni separate, stai cambiando la funzionalità. Inizialmente sta essenzialmente testando quello a < b && b == cper la tua dichiarazione originale di a < b == c.

Un altro esempio:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True

1
OMG, a < b && b == cè lo stesso di a < b == cOO
Kiril Kirov,

9
>>> 0 < 0 == 0
False

Questo è un confronto incatenato. Restituisce vero se ogni confronto a coppie è a sua volta vero. È l'equivalente di(0 < 0) and (0 == 0)

>>> (0) < (0 == 0)
True

Questo è equivalente a quello 0 < Trueche valuta True.

>>> (0 < 0) == 0
True

Questo è equivalente a quello False == 0che valuta True.

>>> 0 < (0 == 0)
True

Equivalente a 0 < Truecui, come sopra, viene valutato True.


7

Guardando lo smontaggio (i byte codici) è ovvio perché 0 < 0 == 0èFalse .

Ecco un'analisi di questa espressione:

>>>import dis

>>>def f():
...    0 < 0 == 0

>>>dis.dis(f)
  2      0 LOAD_CONST               1 (0)
         3 LOAD_CONST               1 (0)
         6 DUP_TOP
         7 ROT_THREE
         8 COMPARE_OP               0 (<)
        11 JUMP_IF_FALSE_OR_POP    23
        14 LOAD_CONST               1 (0)
        17 COMPARE_OP               2 (==)
        20 JUMP_FORWARD             2 (to 25)
   >>   23 ROT_TWO
        24 POP_TOP
   >>   25 POP_TOP
        26 LOAD_CONST               0 (None)
        29 RETURN_VALUE

Linee di avviso 0-8: queste linee controllano se 0 < 0ovviamente ritornaFalse nello stack di Python.

Ora nota la riga 11: JUMP_IF_FALSE_OR_POP 23 questo significa che se 0 < 0ritornaFalse eseguono un salto alla riga 23.

Ora lo 0 < 0è False, quindi viene eseguito il salto, che lascia lo stack con un Falsevalore che restituisce l'intera espressione 0 < 0 == 0, anche se== 0 parte non è nemmeno verificata.

Quindi, per concludere, la risposta è come si dice in altre risposte a questa domanda. 0 < 0 == 0ha un significato speciale. Il compilatore lo valuta in due termini: 0 < 0e 0 == 0. Come con qualsiasi espressione booleana complessa conand tra loro, se il primo fallisce, il secondo non viene nemmeno controllato.

Spero che questo illumini un po 'le cose, e spero davvero che il metodo che ho usato per analizzare questo comportamento inaspettato incoraggerà gli altri a provare lo stesso in futuro.


Non sarebbe solo più semplice elaborarlo dalle specifiche piuttosto che decodificare una particolare implementazione?
David Heffernan,

No. Questa è la risposta breve. Credo che dipenda dalla tua personalità. Se ti riferisci a una vista "scatola nera" e preferisci ottenere le risposte dalle specifiche e dalla documentazione, questa risposta ti confonderà solo. Se ti piace scavare e rivelare gli interni delle cose, allora questa risposta è per te. La tua osservazione che questo reverse engineering è rilevante solo per una particolare implementazione è corretta e deve essere sottolineata, ma non era questo il punto di questa risposta. È una dimostrazione di quanto sia facile in Python dare un'occhiata "sotto il cofano" a coloro che sono abbastanza curiosi.
Sabato

1
Il problema con il reverse engineering è la sua mancanza di potere predittivo. Non è il modo di imparare una nuova lingua.
David Heffernan,

Un'altra cosa che devo aggiungere è che le specifiche e la documentazione non sono sempre complete e nella maggior parte dei casi non forniranno risposte per casi specifici. Quindi, credo, non bisogna aver paura di esplorare, investigare e approfondire quanto è necessario per ottenere le risposte.
Sabato

2

Come altri menzionato x comparison_operator y comparison_operator zè lo zucchero sintattico (x comparison_operator y) and (y comparison_operator z)con il bonus che y viene valutato solo una volta.

Quindi la tua espressione 0 < 0 == 0è davvero (0 < 0) and (0 == 0), che valuta False and Truequale è giusta False.


2

forse questo estratto dai documenti può aiutare:

Questi sono i cosiddetti metodi di "confronto avanzato" e sono chiamati per gli operatori di confronto preferibilmente di __cmp__()seguito. La corrispondenza tra simboli operatori e metodi è la seguente: x<ychiamate x.__lt__(y), x<=ychiamate x.__le__(y), x==ychiamate x.__eq__(y), x!=ye x<>y chiamata x.__ne__(y), x>ychiamate x.__gt__(y)e x>=ychiamate x.__ge__(y).

Un metodo di confronto avanzato può restituire il singleton NotImplementedse non implementa l'operazione per una determinata coppia di argomenti. Per convenzione, Falsee Truevengono restituiti per un confronto riuscito. Tuttavia, questi metodi possono restituire qualsiasi valore, quindi se l'operatore di confronto viene utilizzato in un contesto booleano (ad esempio, nella condizione di un'istruzione if), Python chiamerà bool()il valore per determinare se il risultato è vero o falso.

Non ci sono relazioni implicite tra gli operatori di confronto. La verità di x==ynon implica che x!=y sia falso. Di conseguenza, quando si definisce __eq__(), si dovrebbe anche definire in __ne__()modo che gli operatori si comporteranno come previsto. Vedere il paragrafo su __hash__()per alcune note importanti sulla creazione di oggetti hash che supportano le operazioni di confronto personalizzate e sono utilizzabili come chiavi del dizionario.

Non ci sono versioni di argomenti scambiate di questi metodi (da usare quando l'argomento left non supporta l'operazione ma l'argomento right lo fa); piuttosto, __lt__()e __gt__() sono il riflesso dell'altro, __le__() e __ge__()sono il riflesso dell'altro, __eq__()e __ne__() sono il loro riflesso.

Gli argomenti ai ricchi metodi di confronto non sono mai forzati.

Questi erano confronti ma poiché si stanno concatenando confronti si dovrebbe sapere che:

I confronti possono essere concatenati in modo arbitrario, ad esempio, x < y <= zequivale a x < y and y <= z, tranne per il fatto che y viene valutato solo una volta (ma in entrambi i casi z non viene valutato affatto quando x <y viene trovato falso).

Formalmente, se a, b, c, ..., y, z sono espressioni e op1, op2, ..., opN sono operatori di confronto, quindi a op1 b op2 c ... y opN z è equivalente a op1 b e b op2 c e ... y opN z, tranne per il fatto che ogni espressione viene valutata al massimo una volta.


1

Eccolo, in tutta la sua gloria.

>>> class showme(object):
...   def __init__(self, name, value):
...     self.name, self.value = name, value
...   def __repr__(self):
...     return "<showme %s:%s>" % (self.name, self.value)
...   def __cmp__(self, other):
...     print "cmp(%r, %r)" % (self, other)
...     if type(other) == showme:
...       return cmp(self.value, other.value)
...     else:
...       return cmp(self.value, other)
... 
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>> 

0

Sto pensando che Python stia facendo qualcosa di strano tra la magia. Lo stesso che 1 < 2 < 3significa che 2 è compreso tra 1 e 3.

In questo caso, penso che stia facendo [medio 0] è maggiore di [sinistra 0] e uguale a [destra 0]. Lo 0 centrale non è maggiore di 0, quindi viene considerato falso.

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.