“仅评估一次”对于Python中的链式比较意味着什么?

What does “evaluated only once” mean for chained comparisons in Python?

一个朋友把这件事引起了我的注意,在我指出一个奇怪的地方之后,我们都很困惑。

比如说,python的文档,从至少2.5.1开始就已经说过了(还没有进一步检查过:

Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

我们的困惑在于"Y只评估一次"。

给出了一个简单但做作的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Magic(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
    def __lt__(self, other):
        print("Magic: Called lt on {0}".format(self.name))
        if self.val < other.val:
            return True
        else:
            return False
    def __le__(self, other):
        print("Magic: Called le on {0}".format(self.name))
        if self.val <= other.val:
            return True
        else:
            return False

我们可以得出这样的结果:

1
2
3
4
5
6
7
8
9
10
11
>>> x = Magic("x", 0)
>>> y = Magic("y", 5)
>>> z = Magic("z", 10)
>>>
>>> if x < y <= z:
...     print ("More magic.")
...
Magic: Called lt on x
Magic: Called le on y
More magic.
>>>

从传统意义上讲,这看起来像是"y"被"评估"了两次——一次是在调用x.__lt__(y)并对其进行比较时,另一次是在调用y.__le__(z)时。

因此,考虑到这一点,当python文档说"y只计算一次"时,它们究竟意味着什么?


"表达式"y计算一次。也就是说,在下面的表达式中,函数只执行一次。

1
2
3
4
5
6
7
>>> def five():
...    print 'returning 5'
...    return 5
...
>>> 1 < five() <= 5
returning 5
True

与之相反:

1
2
3
4
>>> 1 < five() and five() <= 5
returning 5
returning 5
True

在被评估的y的上下文中,y是指可能产生副作用的任意表达式。例如:

1
2
3
4
5
6
7
8
9
class Foo(object):
    @property
    def complain(self):
        print("Evaluated!")
        return 2

f = Foo()
print(1 < f.complain < 3) # Prints evaluated once
print(1 < f.complain and f.complain < 3)  # Prints evaluated twice