¿Por qué la expresión 0 <0 == 0 devuelve False en Python?


136

Al investigar Queue.py en Python 2.6, encontré esta construcción que me pareció un poco extraña:

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

Si maxsizees 0, la cola nunca está llena.

Mi pregunta es ¿cómo funciona para este caso? ¿Cómo 0 < 0 == 0se considera falso?

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

¿Es 0 <Verdadero igual a Falso en python?
Marino Šimić

3
@Marino Šimić: Del segundo ejemplo que se muestra en la pregunta del OP >>> (0) < (0 == 0), claramente no lo es.
Martineau

3
Una razón por la que no deberías escribir código como n = 0 < self.maxsize == self._qsize()en primer lugar, en ningún idioma. Si sus ojos tienen que moverse de un lado a otro de la línea varias veces para descubrir lo que está sucediendo, no es una línea bien escrita. Simplemente divídalo en varias líneas.
BlueRaja - Danny Pflughoeft

2
@Blue: estoy de acuerdo con no escribir esa comparación de esa manera, pero dividirla en líneas separadas va un poco por la borda para dos comparaciones. Espero que quieras decir, dividirlo en comparaciones separadas. ;)
Jeff Mercado

2
@Blue: no lo escribí, está en Python 2.6. Solo estaba tratando de entender lo que estaba pasando.
Marcelo Santos

Respuestas:


113

Creo que Python tiene un manejo de casos especial para secuencias de operadores relacionales para hacer que las comparaciones de rango sean fáciles de expresar. Es mucho más agradable poder decir 0 < x <= 5que decir (0 < x) and (x <= 5).

Estas se llaman comparaciones encadenadas . Y ese es un enlace a la documentación para ellos.

Con los otros casos de los que habla, los paréntesis obligan a un operador relacional a aplicarse antes que al otro, por lo que ya no son comparaciones encadenadas. Y dado que Truey Falsetiene valores como enteros, obtiene las respuestas que hace de las versiones entre paréntesis.


Es interesante probar algunas de estas comparaciones y especificar int () y bool (). Me di cuenta de que el valor bool () de cualquier valor distinto de cero es 1. Supongo que nunca hubiera tratado de especificar directamente otra cosa que no sea bool (0) o bool (1) antes de este experimento
mental

¿Pretendía vincular a esta sección en su lugar? docs.python.org/2/reference/expressions.html#comparisons
tavnab

@tavnab: Sí. Trataré de recordar arreglarlo. También voy a verificar el historial de edición. Eso no parece un error que cometería. 😕
Omnifarious

42

Porque

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

es False. Puede encadenar operadores de comparación y se expanden automáticamente en las comparaciones por pares.


EDITAR: aclaración sobre verdadero y falso en Python

En Python Truey Falseson solo instancias de bool, que es una subclase de int. En otras palabras, Truerealmente es solo 1.

El punto de esto es que puede usar el resultado de una comparación booleana exactamente como un entero. Esto lleva a cosas confusas como

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

Pero esto solo sucederá si entre paréntesis las comparaciones para que se evalúen primero. De lo contrario, Python expandirá los operadores de comparación.


2
Ayer vi un uso interesante de los valores booleanos como enteros. La expresión 'success' if result_code == 0 else 'failure'puede reescribirse ya que ('error', 'success')[result_code == 0], antes de esto, nunca había visto un booleano utilizado para seleccionar un elemento en una lista / tupla.
Andrew Clark

'bool' se agregó en algún momento alrededor de Python 2.2.
MRAB

18

El comportamiento extraño que experimentas proviene de la habilidad de las pitones para encadenar las condiciones. Como encuentra que 0 no es menor que 0, decide que toda la expresión se evalúa como falsa. Tan pronto como separe esto en condiciones separadas, cambiará la funcionalidad. Inicialmente está probando esencialmente eso a < b && b == cpara su declaración original de a < b == c.

Otro ejemplo:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True

1
OMG, a < b && b == ces lo mismo que a < b == cOO
Kiril Kirov

9
>>> 0 < 0 == 0
False

Esta es una comparación encadenada. Devuelve verdadero si cada comparación por pares a su vez es verdadera. Es el equivalente a(0 < 0) and (0 == 0)

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

Esto es equivalente a lo 0 < Trueque se evalúa como Verdadero.

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

Esto es equivalente a lo False == 0que se evalúa como Verdadero.

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

Equivalente a lo 0 < Trueque, como anteriormente, se evalúa como Verdadero.


7

Mirando el desmontaje (los códigos de bytes) es obvio por qué 0 < 0 == 0es False.

Aquí hay un análisis de esta expresión:

>>>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 las líneas 0-8: estas líneas verifican si 0 < 0obviamente regresa Falsea la pila de Python.

Ahora observe la línea 11: JUMP_IF_FALSE_OR_POP 23 Esto significa que si 0 < 0regresa, Falserealice un salto a la línea 23.

Ahora, 0 < 0es False, entonces se realiza el salto, que deja la pila con un Falsevalor de retorno para toda la expresión 0 < 0 == 0, aunque la == 0parte ni siquiera esté marcada.

Entonces, para concluir, la respuesta es como se dijo en otras respuestas a esta pregunta. 0 < 0 == 0Tiene un significado especial. El compilador evalúa esto en dos términos: 0 < 0y 0 == 0. Como con cualquier expresión booleana compleja andentre ellas, si la primera falla, la segunda ni siquiera está marcada.

Espero que esto aclare un poco las cosas, y realmente espero que el método que utilicé para analizar este comportamiento inesperado anime a otros a intentar lo mismo en el futuro.


¿No sería más fácil resolverlo a partir de la especificación en lugar de aplicar ingeniería inversa a una implementación en particular?
David Heffernan

No. Esa es la respuesta corta. Creo que depende de tu personalidad. Si se relaciona con una vista de "recuadro negro" y prefiere obtener sus respuestas de las especificaciones y la documentación, esta respuesta solo lo confundirá. Si te gusta profundizar y revelar lo interno de las cosas, entonces esta respuesta es para ti. Su observación de que esta ingeniería inversa es relevante solo para una implementación particular es correcta y debe señalarse, pero ese no era el punto de esta respuesta. Es una demostración de lo fácil que es en Python echar un vistazo "bajo el capó" para aquellos que tienen la curiosidad suficiente.
Sáb

1
El problema con la ingeniería inversa es su falta de poder predictivo. No es la forma de aprender un nuevo idioma.
David Heffernan

Otra cosa que debo agregar es que las especificaciones y la documentación no siempre están completas y en la mayoría de los casos no proporcionarán respuestas para casos tan específicos. Entonces, creo, uno no debe tener miedo de explorar, investigar y llegar a lo más profundo que sea necesario para obtener las respuestas.
Sáb

2

Como se mencionó en otros, x comparison_operator y comparison_operator zes el azúcar sintáctico (x comparison_operator y) and (y comparison_operator z)con la ventaja de que y solo se evalúa una vez.

Entonces su expresión 0 < 0 == 0es realmente (0 < 0) and (0 == 0), lo que evalúa False and Truecuál es la justa False.


2

Tal vez este extracto de los documentos puede ayudar:

Estos son los llamados métodos de "comparación enriquecida", y son llamados para operadores de comparación con preferencia a __cmp__()continuación. La correspondencia entre los símbolos de operador y nombres de los métodos es el siguiente: x<yllamadas x.__lt__(y), x<=yllamadas x.__le__(y), x==yllamadas x.__eq__(y), x!=yy x<>y llamada x.__ne__(y), x>yllamadas x.__gt__(y)y x>=yllamadas x.__ge__(y).

Un método de comparación rico puede devolver el singleton NotImplementedsi no implementa la operación para un par dado de argumentos. Por convención, Falsey Truese devuelven para una comparación exitosa. Sin embargo, estos métodos pueden devolver cualquier valor, por lo que si el operador de comparación se usa en un contexto booleano (por ejemplo, en la condición de una declaración if), Python invocará bool()el valor para determinar si el resultado es verdadero o falso.

No hay relaciones implícitas entre los operadores de comparación. La verdad de x==yno implica que x!=y sea ​​falso. En consecuencia, cuando se define __eq__(), también se debe definir __ne__()para que los operadores se comporten como se espera. Consulte el párrafo __hash__()para obtener algunas notas importantes sobre la creación de objetos hashaable que admiten operaciones de comparación personalizadas y se pueden usar como teclas de diccionario.

No hay versiones de argumentos intercambiados de estos métodos (para ser utilizados cuando el argumento izquierdo no admite la operación pero el argumento derecho sí); más bien, __lt__()y __gt__() son el reflejo del otro, __le__() y __ge__()son el reflejo del otro, __eq__()y __ne__() son su propio reflejo.

Los argumentos a los métodos de comparación enriquecidos nunca son obligados.

Estas fueron comparaciones, pero como estás encadenando comparaciones , debes saber que:

Las comparaciones se pueden encadenar arbitrariamente, por ejemplo, x < y <= zes equivalente a x < y and y <= z, excepto que y se evalúa solo una vez (pero en ambos casos z no se evalúa en absoluto cuando x <y se considera falso).

Formalmente, si a, b, c, ..., y, z son expresiones y op1, op2, ..., opN son operadores de comparación, entonces a op1 b op2 c ... y opN z es equivalente a a op1 b y b op2 c y ... y opN z, excepto que cada expresión se evalúa como máximo una vez.


1

Aquí está, en todo su esplendor.

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

Estoy pensando que Python está haciendo algo extraño entre la magia. Lo mismo que 1 < 2 < 3significa que 2 está entre 1 y 3.

En este caso, creo que está haciendo [middle 0] es mayor que [left 0] e igual a [right 0]. El medio 0 no es mayor que el izquierdo 0, por lo que se evalúa como falso.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.