
On Tue, Oct 26, 2021 at 12:40 PM <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
On 2021-10-26 at 12:12:47 +1100, Chris Angelico <rosuav@gmail.com> wrote:
On Tue, Oct 26, 2021 at 11:44 AM Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
I prefer 1). Easier to understand and debug in examples with side-effects such as def f(a := enter_codes(), b = assign_targets(), c := unlock_missiles(), d = FIRE()): (not that this is something to be particularly encouraged).
It's worth noting that this would call the functions at different times; assign_targets and FIRE would be called when the function is defined, despite not entering the codes and unlocking the missiles until you actually call f().
So much for evaluating default values from left to right; this could be trouble even if the functions themsevles don't have side effects, but merely access data that has been mutated between function definition time and function call time. Requiring that all late bound defaults come after all early bound defaults (which has already come up as a possibility) seems like a reasonable solution.
The difference between early evaluation and late evaluation is that one retains the *value* and the other retains the *expression*. So it's something like:
_b_default = assign_targets(); _d_default = FIRE() def f(a, b, c, d): if a is not set: a = enter_codes() if b is not set: b = _b_default if c is not set: c = unlock_missiles() if d is not set: d = _d_default
Is the phrase/concept "retains the expression" new? Unless it's really a new concept, is there an existing way to say that?
It's sloppy terminology, so the expression is probably new. You'll find similar phenomena in lambda functions, comprehensions, and the like, where you provide an expression that gets evaluated later; but nothing's really "retained", it's really just that the code is run at a particular time. The compiler looks over all of the source code and turns it into something runnable (in the case of CPython, that's bytecode). The code for early-evaluated defaults is part of the execution of the "def" statement; late-evaluated defaults is part of the call to the function itself. Here's an example: def f(): print("Start") def g(x=q()): print("Inside") print("Done") And here's the disassembly, with my annotations:
dis.dis(f) 2 0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 ('Start') 4 CALL_FUNCTION 1 6 POP_TOP
-- here we start defining the function -- 3 8 LOAD_GLOBAL 1 (q) 10 CALL_FUNCTION 0 12 BUILD_TUPLE 1 -- default args are stored in a tuple -- 14 LOAD_CONST 2 (<code object g at 0x7f7bdb4098b0, file "<stdin>", line 3>) 16 MAKE_FUNCTION 1 (defaults) 18 STORE_FAST 0 (g) -- the code is already compiled, so it just attaches the defaults to the existing code object -- 5 20 LOAD_GLOBAL 0 (print) 22 LOAD_CONST 3 ('Done') 24 CALL_FUNCTION 1 26 POP_TOP 28 LOAD_CONST 0 (None) 30 RETURN_VALUE -- this is the body of g() -- Disassembly of <code object g at 0x7f7bdb4098b0, file "<stdin>", line 3>: 4 0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 ('Inside') 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE -- by the time we get into this block of code, args have already been set -- Late-evaluated defaults would slip in just before the print("Inside") line. Technically there's no "expression" that gets "retained", since it's just a matter of where the bytecode gets placed; but in terms of explaining it usefully, the sloppy description is far easier to grok than a detailed look at bytecode - plus, the bytecode is implementation-specific, and not mandated by the language. ChrisA