On Mon, Dec 6, 2021 at 1:45 AM Steven D'Aprano <steve@pearwood.info> wrote:
On Sat, Dec 04, 2021 at 06:11:08PM +0000, Barry Scott wrote:
There are many possible implementation of the late bound idea that could create an object/default expression. But is it reasonable to bother with that added complexity/maintenance burden for a first implementation.
Chris wants to throw the late-bound defaults into the body of the function because that's what we are currently forced to do to emulate late-bound defaults. But we are designing the feature from scratch, and we shouldn't be bound by the limitations of the existing idiom.
Not quite true. I want to have them syntactically as part of the body of the function, and semantically as part of the function call. As a function begins executing, a ton of stuff happens, including allocating arguments to parameters, and providing default values for optional parameters that weren't passed. All I want to change is the way that defaults can be provided. Tell me, what's the first bytecode instruction in the function g here? def f(x): print(x) return lambda: x In current CPython, it's not "LOAD_GLOBAL print". It's "MAKE_CELL x". Conceptually, that's part of the framework of setting up the function - setting up its local variables, providing a place for nonlocals. But it's a bytecode at the start of the function's code object. (I'm not sure when that became a bytecode instruction. It wasn't one in Python 2.7.) CPython bytecode is an implementation detail, and the fact that there's bytecode to do this or that is not part of the language definition.
Encapsulation is good. Putting late-bound expressions in their own function so that they can be tested is a good thing, not a problem to be avoided. Most of us have, from time to time, already moved the late bound default into its own function just to make it easy to test in isolation:
def func(arg=None): if arg is None: arg = _implementation() # and now handle arg
With my strategy, we get that isolation for almost for free. There is no extra effort needed on the programmer's side, no new name or top-level function needed.
And if you want an _implementation function for external testing, you're still welcome to do that. def func(arg=>_implementation()): ... No magic, just perfectly normal coding practices. If something's important enough to test separately, refactor it into a private function so you can call it. Why should this happen automatically for late-bound argument defaults? From what I can tell, the only syntactic constructs which form separate code objects are those which need them for namespacing purposes: class blocks, nested functions, genexps, comprehensions. Default argument values are not separate namespaces, so they shouldn't need dedicated stack frames, dedicated code objects, or anything like that.
Will people take advantage of it? Of course people won't bother if the default is `[]` but beyond a certain level of complexity, people will want to test the default expressions in isolation, to be sure that it does the right thing.
Beyond a certain level of complexity, the fact that argument defaults are in the signature will naturally pressure people to refactor them into their own functions. I don't think we'll see people putting multiline argument default expressions directly into the function header. ChrisA