On Sun, Oct 24, 2021 at 6:18 AM <2qdxy4rzwzuui...@potatochowder.com> wrote: > > The expression would be evaluated in the function's context, having > > available to it everything that the function has. Notably, this is NOT > > the same as the context of the function definition, but this is only > > rarely going to be significant (eg class methods where a bare name in > > an early-bound argument default would come from class scope, but the > > same bare name would come from local scope if late-bound). > > > The purpose of this change is to have the function header define, as > > fully as possible, the function's arguments. Burying part of that > > definition inside the function is arbitrary and unnecessary. > > Those two paragraphs contradict each other. If the expression is > evaluated in the function's context, then said evaluation is (by > definition?) part of the function and not part of its argumens.
The function header is a syntactic construct - the "def" line, any decorators, annotations, etc. But for the late-binding expressions to be useful, they MUST be evaluated in the context of the function body, not its definition. That's the only way that expressions like len(a) can be of value. (Admittedly, this feature would have some value even without that, but it would be extremely surprising and restrictive.) > As a separate matter, are following (admittedly toy) functions (a) an > infinite confusion factory, or (b) a teaching moment? > > def f1(l=[]): > l.append(4) > return l > > def f2(l=:[]): > l.append(4) > return l Teaching moment. Currently, the equivalent second function would be this: def f2(l=None): if l is None: l = [] l.append(4) return l And the whole "early bind or late bind" question is there just the same; the only difference is that the late binding happens somewhere inside the function body, instead of being visible as part of the function's header. (In this toy example, it's the very next line, which isn't a major problem; but in real-world examples, it's often buried deeper in the function, and it's not obvious that passing None really is the same as passing the array's length, or using a system random number generator, or constructing a new list, or whatever it is.) This is also why the evaluation has to happen in the function's context: the two forms should be broadly equivalent. You should be able to explain a late-bound function default argument by saying "it's like using =None and then checking for None in the function body, only it doesn't use None like that". This is, ultimately, the same teaching moment that you can get in classes: class X: items = [] def add_item(self, item): self.items.append(item) class Y: def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) Understanding these distinctions is crucial to understanding what your code is doing. There's no getting away from that. I'm aware that blessing this with nice syntax will likely lead to a lot of people (a) using late-binding everywhere, even if it's unnecessary; or (b) using early-binding, but then treating late-binding as a magic bandaid that fixes problems if you apply it in the right places. Programmers are lazy. We don't always go to the effort of understanding what things truly do. But we can't shackle ourselves just because some people will misuse a feature - we have plenty of footguns in every language, and it's understood that programmers should be allowed to use them if they choose. 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/FO4D3ZCFL2BE6E7ZODYJN23ELMOS75GR/ Code of Conduct: http://python.org/psf/codeofconduct/