On Thu, Dec 2, 2021 at 6:59 PM Brendan Barnwell <brenb...@brenbarn.net> wrote:
>
> On 2021-12-01 23:36, Chris Angelico wrote:
> > That's exactly why it's such a close parallel. The late-evaluated
> > default is just code, nothing else. It's not "stored" in any way - it
> > is evaluated as part of the function beginning execution.
>
>         But it IS stored!  There is no way for it to be evaluated without it
> being stored!
>
>         I know we're talking past each other here but it is quite obvious that
> something has to be stored if it is going to be evaluated later.  You
> can say that it is "just code" but that doesn't change the fact that
> that code has to be stored.  You can say that it is just prepended to
> the function body but that's still storing it.  That is still not
> parallel to a ternary operator in which no part of the expression is
> EVER re-executed unless control flow causes execution to return to that
> same source code line and re-execute it as a whole.

I'm not sure I understand you here. How is the late-bound default
"stored" when one side of a ternary is "not stored"?

>         Actually this raises a question that maybe was answered in the earlier
> thread but if so I forgot: if a function has a late-bound default, will
> the code to evaluate it be stored as part of the function's code object?
>

Yes. To be precise, it is part of the code object's co_code attribute
- the bytecode (or wordcode if you prefer) of the function.

Here's how a ternary if looks:

>>> def f(n):
...     return 0 if n == 0 else 42/n
...
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (n)
              2 LOAD_CONST               1 (0)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE        6 (to 12)
              8 LOAD_CONST               1 (0)
             10 RETURN_VALUE
        >>   12 LOAD_CONST               2 (42)
             14 LOAD_FAST                0 (n)
             16 BINARY_TRUE_DIVIDE
             18 RETURN_VALUE

The "42/n" part is stored in f.__code__.co_code as the part that says
"LOAD_CONST 42, LOAD_FAST n, BINARY_TRUE_DIVIDE". It's not an object.
It's just code - three instructions.

Here's how (in the reference implementation - everything is subject to
change) a late-bound default looks:

>>> def f(x=>[]): print(x)
...
>>> dis.dis(f)
  1           0 QUERY_FAST               0 (x)
              2 POP_JUMP_IF_TRUE         4 (to 8)
              4 BUILD_LIST               0
              6 STORE_FAST               0 (x)
        >>    8 LOAD_GLOBAL              0 (print)
             10 LOAD_FAST                0 (x)
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

The "=>[]" part is stored in f.__code__.co_code as the part that says
"QUERY_FAST x, and if false, BUILD_LIST, STORE_FAST x". It's not an
object. It's four instructions in the bytecode.

In both cases, no part of the expression is ever re-executed. I'm not
understanding the distinction here. Can you explain further please?

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

Reply via email to