
On Thu, Dec 2, 2021 at 6:59 PM Brendan Barnwell <brenbarn@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