On Fri, Dec 3, 2021 at 12:48 PM Steven D'Aprano <st...@pearwood.info> wrote:
>
> On Fri, Dec 03, 2021 at 02:10:12AM +1100, Chris Angelico wrote:
>
> > > > Unfortunately not, since the default expression could refer to other
> > > > parameters, or closure variables, or anything else from the context of
> > > > the called function. So you won't be able to externally evaluate it.
> > >
> > > Why not? Functions can do all those things: refer to other variables, or
> > > closures, or anything else. You can call functions. Are you sure that
> > > this limitation of the default expression is not just a limitation of
> > > your implementation?
> >
> > def f():
> >     a = 1
> >     def f(b, c=>a+b): return c
> >     a = 2
> >     return f
> >
> > If there were a function to represent the late-bound default value for
> > c, what parameters should it accept?
>
> I'm not saying that it *must* be a function. It could be a bare code
> object, that is `eval()`ed. Or something completely new. Dunno.
>
> But you're saying something is impossible, and that seems implausible to
> me, because things that seems *very similar* are totally possible.
>
>
> > How would you externally evaluate this?
>
>
> inner = f()  # Get the inner function.
> default = inner.__code__.__late_defaults__.wibble[1]  # whatever
> try:
>     value = default()
> except NameError:
>     # Well, what did you expect to happen?
>     value = eval(default.__code__, globals(), {'a': 101, 'b': 202})
>
>
> Or something. The point is, rather than dismissing the possibility
> outright, this should be something we discuss, and carefully consider,
> before the PEP is complete.

How, with external calling, are you going to know which name
references to look up, and where to get their values from? This is
already a virtually impossible task in Python, which is why f-strings
cannot be implemented as str.format(**something) for any known meaning
of "something". You cannot get your *current* variable set, much less
the available variables in some other context.

> > And it is potentially a LOT of unnecessary overhead. Consider this edge 
> > case:
> >
> > def f(a, b=>c:=len(a)): ...
> >
> > In what context should the name c be bound?
>
> With late-bound defaults, the expression is evaluated at function call
> time, in the scope of f()'s locals. So c would be a local.

In other words, if you're trying to evaluate b's default externally,
you have to set c in a context that doesn't even exist yet. Is that
correct?

> The third obvious answer is that if either the decision or the
> implementation is really too hard, then make it a syntax error for now,
> and revisit it in the future.

Everything's perfectly well defined, save that you can't externally
evaluate the defaults. You can quite happily use the walrus in a
late-bound default, and it'll bind to the function's locals. (Though I
don't recommend it. I think that that's going to make for
less-readable code than alternatives. Still, it's perfectly legal and
well-defined.)

> > It's a massive amount of completely unnecessary overhead AND a
> > difficult question of which parts belong in the closure and which
> > parts belong as parameters, which means that this is nearly impossible
> > to define usefully.
>
> I've given you two useful definitions.
>
> Its not clear what overhead you are worried about.
>
> Accessing variables in cells is almost as fast as accessing locals, but
> even if they were as slow as globals, premature optimization is the root
> of all evil. Globals are fast enough.
>
> Or are you worried about the memory overhead of the closures? The extra
> cost of fetching and calling the functions when evaluating the defaults?
> None of these things seem to be good reasons to dismiss the idea that
> default expressions should be independent of the function body.

The problem is that you're trying to reference cell variables in a
closure that might not even exist yet, so *just in case* you might
have nonlocals, you have to construct a closure... or find an existing
one. Ill-defined and inefficient.

> > That's not what the example shows. It shows that changing dunder
> > attributes can do this. I'm not sure why you think that the
> > implementation is as restricted as you imply. The assignment to
> > __defaults_extra__ is kinda significant here :)
>
> Ah, well that is not so clear to people who aren't as immersed in the
> implementation as you :-)

Maybe, but I did think that the fact that I was assigning to a dunder
should be obvious to people who are reading here :)

> Messing about with function dunders can do weird shit:
>
> >>> def func(a=1, b=2):
> ...     return a+b
> ...
> >>> func.__defaults__ = (1, 2, 3, 4, 5)
> >>> func()
> 9
>
> I wouldn't worry about it.
>

Good, so, not an abomination. (In your opinion. Others are, of course,
free to abominate as they please.)

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

Reply via email to