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/

Reply via email to