¿Qué significa “evaluado solo una vez” para las comparaciones encadenadas en Python?

Un amigo me llamó la atención, y después de señalar una rareza, los dos estamos confundidos.

Los documentos de Python, digamos, y han dicho desde al menos 2.5.1 (no he comprobado más atrás:

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

Nuestra confusión radica en el significado de “y se evalúa solo una vez”.

Dada una clase simple pero artificial:

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 

Podemos producir este resultado:

 >>> x = Magic("x", 0) >>> y = Magic("y", 5) >>> z = Magic("z", 10) >>> >>> if x < y >> 

Esto ciertamente parece que ‘y’ es, en un sentido tradicional, “evaluado” dos veces, una vez cuando se llama x.__lt__(y) y se realiza una comparación en él, y una vez cuando se llama y.__le__(z) .

Entonces, con esto en mente, ¿qué significan exactamente los documentos de Python cuando dicen “y se evalúa solo una vez”?

La ‘expresión’ y se evalúa una vez. Es decir, en la siguiente expresión, la función se ejecuta solo una vez.

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

Opuesto a:

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

En el contexto de y que se evalúa, y se entiende como una expresión arbitraria que podría tener efectos secundarios. Por ejemplo:

 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