`foo < bar < baz` 究竟调用了哪些方法?

9

在 Python 中,我们可以这样说:

if foo < bar < baz:
    do something.

同样地,我们可以像这样重载比较运算符:
class Bar:
    def __lt__(self, other):
        do something else

但是,这些区间比较的操作数类型使用哪些方法进行调用?上述内容是否等同于:
if foo.__lt__(bar) and bar.__lt__(baz):
    do something.

编辑:关于S.Lott的问题,这里提供一些输出结果以帮助说明实际发生的情况。
>>> class Bar:
    def __init__(self, name):
        self.name = name
        print('__init__', self.name)
    def __lt__(self, other):
        print('__lt__', self.name, other.name)
        return self.name < other.name

>>> Bar('a') < Bar('b') < Bar('c')
('__init__', 'a')
('__init__', 'b')
('__lt__', 'a', 'b')
('__init__', 'c')
('__lt__', 'b', 'c')
True
>>> Bar('b') < Bar('a') < Bar('c')
('__init__', 'b')
('__init__', 'a')
('__lt__', 'b', 'a')
False
>>> 

+1:你已经回答了自己的问题。 -1:显然,你不需要在这里提问,因为找到答案比提问更容易和更快。 - S.Lott
4个回答

13
if foo < bar < baz:

等同于

if foo < bar and bar < baz:

有一个重要的区别:如果bar是可变的,它将被缓存。 也就是说:

if foo < bar() < baz:

等同于

tmp = bar()
if foo < tmp and tmp < baz:

但是为了回答你的问题,最终结果将会是:

if foo.__lt__(bar) and bar.__lt__(baz):

哇,我帖子的前三行本来和你一模一样,只有一个冒号不同。加一分给你。 ;) - Nathan Ernst

4

你是正确的:

class Bar:
    def __init__(self, name):
        self.name = name
    def __lt__(self, other):
        print('__lt__', self.name, other.name)
        return True

a,b,c = Bar('a'), Bar('b'), Bar('c')

a < b < c

输出:

('__lt__', 'a', 'b')
('__lt__', 'b', 'c')
True

在某种程度上,我也非常关注and是否被使用,或者是否还有其他聪明的方法。 - SingleNegationElimination
经过一些测试,看起来它必须做某些基本相似的事情。我可以通过第一个比较为假来安排第二个比较被短路。 - SingleNegationElimination
@TokenMacGuy:正如您在我的第二个示例中所看到的,它不仅仅是一个简单的“and”,因为bar可能是一个可变函数。 - Mike Axiak
1
我接受了这个答案,因为它提供了足够的代码,让我在交互式解释器中使用并给我所需的所有信息,尽管两个答案都非常有用。 - SingleNegationElimination

3
它使用连续调用小于比较运算符:
>>> import dis
>>> def foo(a,b,c):
...     return a < b < c
... 
>>> dis.dis(foo)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 DUP_TOP             
              7 ROT_THREE           
              8 COMPARE_OP               0 (<)
             11 JUMP_IF_FALSE            8 (to 22)
             14 POP_TOP             
             15 LOAD_FAST                2 (c)
             18 COMPARE_OP               0 (<)
             21 RETURN_VALUE        
        >>   22 ROT_TWO             
             23 POP_TOP             
             24 RETURN_VALUE        

+1 生成的代码。这仅仅强调了我原本计划用于类的可爱语法不会安全地发生。但是我学到了很多,值得深入研究。 - SingleNegationElimination

1

它调用特殊方法__lt__(),如果需要,它将调用__nonzero__()来强制将__lt__()的结果转换为布尔值。令人惊讶的是(至少对我来说),没有__and__()方法来覆盖and运算符。

这是一个测试程序:

#!/usr/bin/env python

class Bar:
    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        print "%s.__lt__(%s)" % (self, other)
        return Bar("%s.__lt__(%s)" % (self, other))

    def __nonzero__(self):
        print "%s.__nonzero__()" % (self)
        return True

    def __str__(self):
        return self.value

foo = Bar("foo")
bar = Bar("bar")
baz = Bar("baz")

if foo < bar < baz:
    pass

输出:

foo.__lt__(bar)
foo.__lt__(bar).__nonzero__()
bar.__lt__(baz)
bar.__lt__(baz).__nonzero__()

在这个上下文中,“and”不是运算符,而是一种描述控制流的机制(显然,在另一个上下文中有一个按位“and”,其中有一个适当的运算符和一个“__and__”覆盖)。请记住,“and”实际上并不比较任何东西,它只评估表达式的每个成员的真实性,并在找到一个错误时退出。要求重写“and”的方法就像要求重写“if”的方法一样。 - Nick Bastin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接