为什么表达式0 <0 == 0在Python中返回False?


136

在Python 2.6中查看Queue.py时,我发现这个构造有点奇怪:

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

如果maxsize为0,则队列永远不会满。

我的问题是在这种情况下如何运作?如何0 < 0 == 0被认为是错误的?

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

在Python中0 <True是否等于False?
MarinoŠimić'11

3
@MarinoŠimić:从OP问题中显示的第二个示例来看>>> (0) < (0 == 0),显然不是。
martineau 2011年

3
n = 0 < self.maxsize == self._qsize()首先,您不应该使用任何语言编写代码的一个原因。如果您的眼睛必须多次划过来回线以弄清发生了什么,这不是写得很好的线。只需将其分成几行即可。
BlueRaja-Danny Pflughoeft

2
@Blue:我同意不以这种方式编写这样的比较,但是将其分成几行会导致两次比较有些落伍。希望您的意思是将其拆分为单独的比较。;)
Jeff Mercado

2
@Blue:我没有编写它,它在Python 2.6中。我只是想了解发生了什么。
马塞洛·桑托斯

Answers:


113

我相信Python对关系运算符的序列有特殊的处理方式,以使范围比较易于表达。可以说更好0 < x <= 5比好得多(0 < x) and (x <= 5)

这些称为链接比较。这是他们文档的链接。

在您谈论的其他情况下,括号会强制在一个关系运算符之前应用一个关系运算符,因此它们不再是链式比较。并且由于True和的False值都是整数,因此您可以从括号中得到答案。


尝试其中的一些比较并指定int()和bool()很有趣。我意识到,任何非零的bool()都是1。猜想我从来没有尝试过直接指定bool(0)或bool(1)之外的任何东西
j_syk 2011年


@tavnab-是的。我会尽力记住修复它。我还将检查编辑历史记录。这似乎不是我会犯的错误。😕–
万能的

42

因为

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

False。您可以将比较运算符链接在一起,它们会自动扩展为成对比较。


编辑-关于Python中正确与错误的说明

在Python中TrueFalse仅仅是的实例bool,它是的子类int。换句话说,True确实只有1。

这样做的目的是,您可以像完全使用整数一样使用布尔比较的结果。这导致诸如

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

但是,只有在您对比较加括号以使它们首先被评估时,这些情况才会发生。否则,Python将扩展比较运算符。


2
昨天我看到了布尔值用作整数的一种有趣用法。该表达式'success' if result_code == 0 else 'failure'可以重写为('error', 'success')[result_code == 0],在此之前,我从未见过用于选择列表/元组中的项的布尔值。
安德鲁·克拉克

在Python 2.2的某个时候添加了“ bool”。
MRAB

18

您遇到的奇怪行为来自python链接条件的能力。由于发现0不小于0,因此它决定整个表达式的计算结果为false。一旦将其分解为单独的条件,就在更改功能。最初,它实际上是在测试a < b && b == c您对的原始声明a < b == c

另一个例子:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True

1
OMG,a < b && b == c相同a < b == cOO
基里尔·基洛夫

9
>>> 0 < 0 == 0
False

这是一个链式比较。如果每个成对比较依次为true,则返回true。相当于(0 < 0) and (0 == 0)

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

这等效0 < True于其值为True。

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

这等效False == 0于其值为True。

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

0 < True如上所述,与之等效的结果为True。


7

查看反汇编(字节码),很明显为什么0 < 0 == 0False

这是对此表达式的分析:

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

注意第0-8行:这些行检查是否0 < 0明显返回False了python堆栈。

现在注意第11行:JUMP_IF_FALSE_OR_POP 23 这意味着如果0 < 0返回,则False跳到第23行。

现在,0 < 0False,因此进行了跳转,即使堆栈中的部分甚至没有被检查,它也会False为堆栈留下a ,这是整个表达式的返回值。0 < 0 == 0== 0

因此,总而言之,答案就像对该问题的其他答案中所说的那样。 0 < 0 == 0有特殊的意义。编译器将其评估为两个术语:0 < 00 == 0。与任何复杂的布尔表达式一样and它们之间的一样,如果第一个失败,则甚至不会检查第二个。

希望这对我们有所启发,并且我真的希望我用来分析这种意外行为的方法会鼓励其他人将来尝试相同的方法。


从规范中解决问题,而不是对一个特定的实现进行逆向工程会更容易吗?
David Heffernan 2014年

不,这是简短的答案。我相信这取决于你的个性。如果您涉及“黑匣子”视图,并且希望从规格和文档中获取答案,那么此答案只会使您感到困惑。如果您想挖掘并揭示事物的内部结构,那么此答案适合您。您说这种逆向工程仅与一个特定的实现有关,这是正确的,必须指出,但这并不是答案的重点。这是一个演示,它说明了python对那些足够好奇的人来说“很容易理解”。
星期六2014年

1
逆向工程的问题是缺乏预测能力。这不是学习新语言的方法。
David Heffernan

我必须补充的另一件事是,规格和文档并不总是完整的,并且在大多数情况下不会为此类特定情况提供答案。然后,我相信,一定不要害怕探索,调查并深入了解获得答案所需的一切。
2014年

2

正如其他人提到的那样x comparison_operator y comparison_operator z,语法糖(x comparison_operator y) and (y comparison_operator z)的优势在于y仅被评估一次。

因此,您的表情0 < 0 == 0是真的(0 < 0) and (0 == 0),它的评估False and True结果是公正的False


2

也许从这个摘录文档可以帮助:

这些是所谓的“丰富比较”方法,在__cmp__()下面优先于比较运算符。运算符符号和方法名之间的对应关系如下:x<y呼叫 x.__lt__(y)x<=y呼叫x.__le__(y)x==y呼叫x.__eq__(y)x!=yx<>y 呼叫x.__ne__(y)x>y呼叫 x.__gt__(y),和x>=y呼叫 x.__ge__(y)

如果富比较方法NotImplemented未实现给定参数对的操作,则可能返回单例。按照惯例,False并将True其返回以进行成功比较。但是,这些方法可以返回任何值,因此,如果在布尔上下文中使用比较运算符(例如,在if语句的条件下),Python将调用bool()该值以确定结果是true还是false。

比较运算符之间没有隐含的关系。的真相x==y并不意味着那x!=y 是错误的。因此,在定义时 __eq__(),还应该定义一个,__ne__()以便操作符能够按预期运行。有关__hash__()创建可哈希对象(支持自定义比较操作并可用作字典键)的一些重要说明,请参见上的段落。

这些方法没有交换参数版本(当left参数不支持该操作但right参数支持该操作时使用);相反,__lt__()and __gt__() 是彼此的反射,__le__() and __ge__()是彼此的反射,and __eq__()and __ne__() 是自己的反射。

丰富比较方法的论点永远不会被强迫。

这些是比较,但是由于要链接比较,因此您应该知道:

可以任意链接比较,例如x < y <= z与等效x < y and y <= z,除了y仅被评估一次(但是在两种情况下,当x <y为假时,z都不被评估)。

形式上,如果a,b,c,...,y,z是表达式,而op1,op2,...,opN是比较运算符,则op1 b op2 c ... y opN z等效于op1 b和b op2 c和... y opN z,除了每个表达式最多计算一次。


1

这就是它的全部荣耀。

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

我在想Python在魔术之间做得很奇怪。与1 < 2 < 3均值2 相同,介于1和3之间。

在这种情况下,我认为它正在执行[中间0]大于[左0]并等于[右0]。中间0不大于左边0,因此它的值为false。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.