On 2021-10-24 at 06:54:36 +1100,
Chris Angelico <ros...@gmail.com> wrote:

> 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.

If you mean that def statements and decorators run at compile time, then
I agree.  If you mean something else, then I don't understand.

> 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.)

I think we're saying the same thing, but drawing different conclusions.
I agree with everything in the first paragraph I quoted above, but I
can't make the leap to claiming that late binding is part of defining
the function's arguments.  You say "late binding of function arguments";
I say "the part of the function that translates the arguments into
something useful for the algorithn the function encapsulates."

> > 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.)

It's only not obvious if the documentation is lacking, or the tools are
lacking, or the programmer is lacking.  The deeper "it" is in the
function, the more you make my point that it's part of the function
itself and not part of setting up the arguments.

> 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.

Understanding the difference between defining a class and instantiating
that class is crucial, as is noticing the very different source code
contexts in which X.items and self.item are created.  I agree.

Stuff in class definitions (X.items, X.add_item, Y.__init__, Y.add_item)
happens when X is created, arguably at compile time.  The code inside
the function suites (looking up and otherwise manipulating self.items)
happens later, arguably at run-time.

In f1, everything in the "def" statement happens when f1 is defined.  In
f2, part of the "def" statement (i.e., defining f2) happens when f2 is
defined (at compile-time), but the other part (the logic surrounding l
and its default value) happens when f2 is called (at run-time).

> 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.

I won't disagree.  Maybe it's just that I am the opposite of sympathetic
to the itches (and those itches' underlying causes) that this particular
potential footgun scratches.

Curiously, for many of the same reasons, I think I'm with you that:

    def get_expensive(self):
        if not self.expensive:
            self.expensive = expensive()
        return self.expensive

is better (or at least not worse) than:

    def get_expensive(self):
        return self.expensive or (self.expensive := expensive())
_______________________________________________
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/6LAZRZP2UKRQYCPAW3UHP5SIL7KP352Q/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to