On Wed, Jan 13, 2021 at 4:24 AM Richard Damon
On 1/12/21 10:53 AM, Mark Shannon wrote:
Hi everyone,
Should the optimizer eliminate tests that it can prove have no effect on the control flow of the program, even if that may eliminate some side effects in __bool__()?
For several years we have converted
if a and b: ...
to
if a: if b: ...
which are equivalent, unless bool(a) has side effects the second time it is called.
In master we convert `if x: pass` to `pass` which is equivalent, unless bool(x) has side effects the first time it is called. This is a recent change.
This is one of those "easy to fix, if we can decide on the semantics" bugs.
Submit your thoughts to https://bugs.python.org/issue42899, please.
Cheers, Mark.
One key point about 'and' and 'or' is that those operators are defined to be 'short circuiting', i.e. that with a and b, that if a is false, then b is not evaluated at all. This can be important if a is a condition that test if the expression b is 'safe' to evaluate. In fact, isn't it true that 'a and b' is defined to be the equivalent to: 'a if not a else b' so b doesn't need to be evaluated unless a is truthy.
Yes, the shortcircuiting behaviour isn't in question. But consider: class A: def __bool__(self): print("A().__bool__") return False def f(): print("if A() and A()") if A() and A(): x = 1 print("cond = A() and A()") cond = A() and A() if cond: x = 1 f() And once you've run the code, disassemble the function for extra insight. There's a very definite difference here, and it's the same difference as: for x in thing: and for x in iter(thing): I'd be fine with documenting that __bool__ is (guaranteed to be) called only if the interpreter needs to know the result, leaving open the option for things like this to be optimized out. That'd leave open the option for "foo() if x else foo()" to be optimized down to just "foo()", although I don't think that particular one is needed.
I know that I would be very surpised if a statement like
if foo():
pass
ended up optimizing itself and not calling foo(), which is only one step away from the quoted case of
if b:
pass
which is the equivalent to
if b.__bool__():
pass
Yes, they do look similar. The difference is that calling __bool__ is under the interpreter's control, and it's easier to document that it be assumed to be side-effect-free.
Yes, perhaps there is more of an expectation that __bool__() is innocuous, but is that a an assumption that should be baked into the language.
I think so. Consider that sometimes other dunders won't be called, if the interpreter believes it's not necessary: class A(float): def __index__(self): print("A().__index__") return 10 class B(int): def __index__(self): print("B().__index__") return 10 print(range(20)[A():B()]) If it's a subclass of float, slicing will call __index__, but if it's a subclass of int, Python knows already that it can use the internal integer value. ChrisA