Что означает "оценивается только один раз" для сравнения в Python?

Друг привлек это к моему вниманию, и после того, как я указал на странность, мы оба смущены.

Документы Python, скажем, и сказали, по крайней мере, с 2.5.1 (еще не проверены далее:

Сравнение может быть скоблено произвольно, например, x < y <= z эквивалентно x < y и y <= z, за исключением того, что y оценивается только один раз (но в обоих случаях z вообще не оценивается, когда x < y считается ложным).

Наша путаница заключается в значении "y оценивается только один раз".

Учитывая простой, но надуманный класс:

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

Мы можем произвести этот результат:

>>> 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 оценивается только один раз"?

Ответ 1

"выражение" y оценивается один раз. I.e., в следующем выражении, функция выполняется только один раз.

>>> def five():
...    print 'returning 5'
...    return 5
... 
>>> 1 < five() <= 5
returning 5
True

В отличие от:

>>> 1 < five() and five() <= 5
returning 5
returning 5
True

Ответ 2

В контексте оценки y, y подразумевается как произвольное выражение, которое может иметь побочные эффекты. Например:

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