On Fri, 17 Jun 2022 at 22:55, Andrew Jaffe <a.h.ja...@gmail.com> wrote:
>
> First, let me state that I am in favour of the proposal (although still
> mildle prefer the ":=" spelling).
>
> On 17/06/2022 13:33, Chris Angelico wrote:
> > On Fri, 17 Jun 2022 at 22:14, Steven D'Aprano <st...@pearwood.info> wrote:
> >>
> >> If we have:
> >> ```
> >> items = ['spam', 'eggs']
> >> def frob(n=>len(items), items=[]):
> >>      print(n)
> >> ```
> >> we cannot even tell whether `frob()` will print 0 or 2 or raise an
> >> exception.
> >
> > It will either print 0 or raise UnboundLocalError. There is no
> > circumstance in which it will legally print 2.
> >
> >> Under the PEP though, this behaviour is underspecified. The PEP
> >> describes this case as implementation dependent. Any of the following
> >> behaviours would be legal when `frob()` is called:
> >>
> >> * n=>len(items) evaluates the parameter `items`, *after* it gets
> >>    bound to the default of [], and so n=0 (that is, it has the same
> >>    semantics as the status quo);
> >
> > Yes, this is legal.
> >
> >> * n=>len(items) evaluates the parameter `items`, but it isn't bound
> >>    to a value yet (because `items` occurs to the right of n), and so
> >>    evaluating the default raises (presumably) UnboundLocalError;
> >
> > Yes, this is legal.
> >
> >> * n=>len(items) evaluates the variable items from the surrounding scope,
> >>    and so evaluates to n=2; if no such variable exists, it will presumably
> >>    raise NameError.
> >
> > No, this makes no sense whatsoever. In Python, a parameter is
> > (effectively) assigned to within the function, and therefore *any*
> > reference to it *must* refer to the local, not to any surrounding
> > scope. Late-bound defaults do not change this fundamental.
>
> I understand this is unambiguous, but it is nonetheless potentially
> confusing: normal, immediate-evaluation arguments do, of course, have
> access to enclosing scope, and so one might be led to believe that this
> is still possible.

That's because immediate-evaluation is like this:

_default = SOME_EXPRESSION
def func(n=None):
    if n is None: n = _default

Whereas late evaluation is like this:

def func(n=None):
    if n is None: n = SOME_EXPRESSION

Unfortunately, there's not going to be any way to resolve this. The
entire point of this feature is to be able to do things that can't be
done with early evaluation, and that includes referring to other
arguments, so it fundamentally has to be done in the function's scope.
Maybe it would have been convenient for Python to define that function
defaults are *always* evaluated in the function's scope, but (a) that
ship has well and truly sailed, and (b) I'm not sure that that would
be better anyway - the current behaviour lets you do an easy snapshot
by writing something like "i=i", so it has the same name on the inside
that it has on the outside.

For the most part, it's not a problem; scopes are nested, so you can
happily refer to a name in an enclosing scope. Exceptions include
names that are shadowed, class-level names (but you'll usually be able
to write "self.X" or "cls.X" so it's just a difference of spelling),
and possibly some quirks of closures, although most of those are a
consequence of timing rather than scoping.

> >> With the behaviour unspecified, we can't predict whether the above
> >> frob() example is legal or what it will do if it is. It could vary not
> >> only between CPython and other Pythons, but from one version of CPython
> >> and another.
> >
> > That is correct. This issue ONLY happens if a late-bound default
> > refers to an early-bound argument that comes to the right of it in the
> > argument list, and the ONLY possible results are UnboundLocalError and
> > getting the value.
>
> Is there a *reason* why you are leaving this unspecified? To put it more
> baldly, is there any reason (e.g., difficulty of parsing?) why allowing
> these "forward" references should *not* be allowed? It seems that
> "n=>len(items), items=[]" might be an important use case.
>

Yes. The alternative is that I make it
"reference-implementation-defined", and I've seen so much of that that
I don't want to lock that in. Just because the way I happen to have
implemented it allows for the late-bound defaults to refer to
early-bound arguments to their right, I don't want to lock the
language into behaving that way forever; conversely, I don't want to
have a non-compliant reference implementation based on a definition of
"all arguments are assigned left to right", which is much cleaner and
simpler to describe, but a lot harder to implement. I want the
language to be open to the cleaner definition, while permitting the
"arguments are assigned left to right in two stages" implementation as
well.

That's the only distinction though. And if you simply place all
late-bound defaults to the right of all early-bound defaults, there
won't be any problem, ever.

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/SANM2XP3S47YIHKBX6GJEIJWLIDW3SOQ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to