On Thu, Dec 2, 2021 at 6:59 PM Brendan Barnwell <[email protected]> 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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/7QV4GCAMP65H77W7RM732IHNOCPHOQSW/
Code of Conduct: http://python.org/psf/codeofconduct/