Por que a expressão 0 <0 == 0 retorna False em Python?


136

Olhando para Queue.py no Python 2.6, achei essa construção que achei um pouco estranha:

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 maxsizefor 0, a fila nunca estará cheia.

Minha pergunta é como isso funciona para este caso? Como 0 < 0 == 0é considerado falso?

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

0 <True é igual a False em python?
Marino Šimić

3
@Marino Šimić: Do segundo exemplo mostrado na pergunta do OP >>> (0) < (0 == 0), claramente não é.
Martineau

3
Um motivo pelo qual você não deve escrever código, como n = 0 < self.maxsize == self._qsize()em primeiro lugar, em qualquer idioma. Se seus olhos precisam percorrer a linha várias vezes para descobrir o que está acontecendo, não é uma linha bem escrita. Apenas divida-o em várias linhas.
BlueRaja - Danny Pflughoeft

2
@ Blue: Concordo em não escrever essa comparação dessa maneira, mas dividi-la em linhas separadas está exagerando em duas comparações. Espero que você queira dizer, divida-o em comparações separadas. ;)
Jeff Mercado

2
@ Blue: Eu não escrevi, é em Python 2.6. Eu só estava tentando entender o que estava acontecendo.
Marcelo Santos

Respostas:


113

Acredito que o Python tenha um tratamento especial de caso para sequências de operadores relacionais para facilitar a expressão de comparações de intervalo. É muito melhor poder dizer 0 < x <= 5do que dizer (0 < x) and (x <= 5).

Estes são chamados comparações encadeadas . E esse é um link para a documentação para eles.

Nos outros casos mencionados, os parênteses forçam um operador relacional a ser aplicado antes do outro e, portanto, não são mais comparações encadeadas. E desde que Truee Falsetenha valores como números inteiros, você obtém as respostas das versões entre parênteses.


é interessante tentar algumas dessas comparações e especificar int () e bool (). Percebi que o bool () de qualquer diferente de zero é 1. Acho que nunca teria tentado especificar diretamente nada além de bool (0) ou bool (1) antes desse experimento
mental

Você pretendia vincular a esta seção? docs.python.org/2/reference/expressions.html#comparisons
tavnab

@tavnab - Sim. Vou tentar lembrar de corrigi-lo. Também vou verificar o histórico de edições. Isso não parece um erro que eu cometeria. 😕
Onívoro 29/0318

42

Porque

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

é False. Você pode encadear operadores de comparação e eles são expandidos automaticamente nas comparações aos pares.


EDIT - esclarecimentos sobre True e False em Python

Em Python Truee Falsesão apenas instâncias de bool, que é uma subclasse de int. Em outras palavras, Trueé realmente apenas 1.

O ponto disso é que você pode usar o resultado de uma comparação booleana exatamente como um número inteiro. Isso leva a coisas confusas como

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

Mas isso só acontecerá se você colocar as comparações entre parênteses para que sejam avaliadas primeiro. Caso contrário, o Python expandirá os operadores de comparação.


2
Eu vi um uso interessante para valores booleanos sendo usados ​​como números inteiros ontem. A expressão 'success' if result_code == 0 else 'failure'pode ser reescrita ('error', 'success')[result_code == 0], pois antes disso eu nunca tinha visto um booleano usado para selecionar um item em uma lista / tupla.
Andrew Clark

'bool' foi adicionado em algum momento no Python 2.2.
MRAB

18

O comportamento estranho que você experimenta vem da capacidade dos pítons de encadear condições. Como encontra 0 não é menor que 0, decide que a expressão inteira é avaliada como falsa. Assim que você separar isso em condições separadas, você estará alterando a funcionalidade. Inicialmente, ele está essencialmente testando isso a < b && b == cpara sua declaração original de a < b == c.

Outro exemplo:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True

1
OMG, a < b && b == cé o mesmo que a < b == cOO
Kiril Kirov

9
>>> 0 < 0 == 0
False

Esta é uma comparação encadeada. Retorna true se cada comparação por pares for verdadeira. É o equivalente a(0 < 0) and (0 == 0)

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

Isso é equivalente ao 0 < Trueque é avaliado como True.

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

Isso é equivalente ao False == 0que é avaliado como True.

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

Equivalente ao 0 < Truequal, como acima, avalia como True.


7

Olhando para a desmontagem (a códigos de bytes), é óbvio porque 0 < 0 == 0é False.

Aqui está uma análise dessa expressão:

>>>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

Observe as linhas de 0 a 8: essas linhas verificam se o 0 < 0que obviamente retorna Falseà pilha python.

Agora observe a linha 11: JUMP_IF_FALSE_OR_POP 23 Isso significa que, se 0 < 0retornos, Falseexecute um salto para a linha 23.

Agora, 0 < 0é False, então o salto é dado, o que deixa a pilha com um Falsevalor de retorno para toda a expressão 0 < 0 == 0, mesmo que a == 0parte nem seja verificada.

Portanto, para concluir, a resposta é como a dita em outras respostas a essa pergunta. 0 < 0 == 0tem um significado especial. O compilador avalia isso em dois termos: 0 < 0e 0 == 0. Como em qualquer expressão booleana complexa andentre elas, se a primeira falhar, a segunda nem será verificada.

Espero que isso ilumine um pouco as coisas, e realmente espero que o método que eu usei para analisar esse comportamento inesperado incentive outras pessoas a tentarem o mesmo no futuro.


Não seria mais fácil resolvê-lo a partir das especificações do que fazer engenharia reversa em uma implementação específica?
David Heffernan

Não. Essa é a resposta curta. Eu acredito que isso depende da sua personalidade. Se você se relaciona com uma exibição de "caixa preta" e prefere obter suas respostas com especificações e documentação, essa resposta apenas o confunde. Se você gosta de se aprofundar e revelar os aspectos internos, então esta resposta é para você. Sua observação de que essa engenharia reversa é relevante apenas para uma implementação específica está correta e deve ser apontada, mas esse não foi o objetivo desta resposta. É uma demonstração de como é fácil em python dar uma olhada "por baixo do capô" para aqueles que têm curiosidade suficiente.
SATA

1
O problema da engenharia reversa é a falta de poder preditivo. Não é o caminho para aprender um novo idioma.
David Heffernan

Outra coisa que devo acrescentar é que as especificações e documentações nem sempre são completas e, na maioria dos casos, não fornecem respostas para casos específicos. Então, acredito, não se deve ter medo de explorar, investigar e se aprofundar o máximo necessário para obter as respostas.
SATA

2

Como já x comparison_operator y comparison_operator zfoi mencionado, o açúcar sintático é (x comparison_operator y) and (y comparison_operator z)o bônus que y é avaliado apenas uma vez.

Portanto, sua expressão 0 < 0 == 0é realmente (0 < 0) and (0 == 0), o que avalia False and Truequal é justo False.


2

talvez este trecho dos documentos possa ajudar:

Estes são os chamados métodos de "comparação rica" ​​e são chamados para operadores de comparação, de preferência __cmp__()abaixo. A correspondência entre símbolos de operadores e nomes método é o seguinte: x<yas chamadas x.__lt__(y), x<=ychamadas x.__le__(y), x==ychamadas x.__eq__(y), x!=ye x<>y chamada x.__ne__(y), x>ychamadas x.__gt__(y), e x>=ychamadas x.__ge__(y).

Um método de comparação rico pode retornar o singleton NotImplementedse ele não implementar a operação para um determinado par de argumentos. Por convenção, Falsee Truesão retornados para uma comparação bem-sucedida. No entanto, esses métodos podem retornar qualquer valor, portanto, se o operador de comparação for usado em um contexto booleano (por exemplo, na condição de uma instrução if), o Python chamará bool()o valor para determinar se o resultado é verdadeiro ou falso.

Não há relacionamentos implícitos entre os operadores de comparação. A verdade de x==ynão implica que isso x!=y seja falso. Assim, ao definir __eq__(), deve-se definir também __ne__()para que os operadores se comportem conforme o esperado. Consulte o parágrafo em __hash__()para algumas notas importantes sobre a criação de objetos hash que suportam operações de comparação personalizadas e são utilizáveis ​​como chaves de dicionário.

Não há versões de argumento trocado desses métodos (a serem usadas quando o argumento esquerdo não suporta a operação, mas o argumento correto); sim, __lt__()e __gt__() são reflexo um do outro, __le__() e __ge__()são reflexo um do outro, e __eq__()e __ne__() são a sua própria reflexão.

Argumentos para métodos de comparação sofisticados nunca são coagidos.

Essas foram comparações, mas como você está encadeando comparações, você deve saber que:

As comparações podem ser encadeadas arbitrariamente, por exemplo, x < y <= zé equivalente a x < y and y <= z, exceto que y é avaliado apenas uma vez (mas em ambos os casos, z não é avaliado quando x <y é considerado falso).

Formalmente, se a, b, c, ..., y, z são expressões e op1, op2, ..., opN são operadores de comparação, então a op1 b op2 c ... y opN z é equivalente a op1 b e b op2 c e ... y opN z, exceto que cada expressão é avaliada no máximo uma vez.


1

Aqui está em toda a sua glória.

>>> 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

Eu estou pensando que Python está fazendo isso é estranho entre magia. O mesmo que a 1 < 2 < 3média 2 está entre 1 e 3.

Nesse caso, acho que está fazendo [o meio 0] é maior que [esquerdo 0] e igual a [direito 0]. O meio 0 não é maior que o esquerdo 0, portanto, avalia como falso.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.