On Tue, Jan 12, 2021 at 9:51 AM Chris Angelico <rosuav@gmail.com> wrote:
On Wed, Jan 13, 2021 at 4:24 AM Richard Damon <Richard@damon-family.org> wrote:
>
> 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.

https://snarky.ca/unravelling-boolean-operations/ if you want the gory details.
 

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 think that's the key question here: it is a language change, but is it one we want (specifically in this case and in general)? It will require a language reference change at least, and as it has been shown, some people don't expect Python to take shortcuts in execution knowing full well that CPython dutifully executes things. So saying we only call __bool__() if necessary is definitely a change. But then this begs the question of whether we want to do this more widely in the language in the name of optimization, or not in the name of consistency that a user can easily reason about.
 

> 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.

https://snarky.ca/unravelling-not-in-python/ 😁 But basically, https://docs.python.org/3.8/reference/datamodel.html#object.__index__ says "called if conversion to an int is necessary" which isn't the case when something is already an int.
 

ChrisA
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/GHKDF6YE3D43JPWS7GVG34FVPJNYE5SO/
Code of Conduct: http://python.org/psf/codeofconduct/