On Mon, 1 Nov 2021, Chris Angelico wrote:
This is incompatible with the existing __get__ method, so it should
get a different name. Also, functions have a __get__ method, so you
definitely don't want to have everything that takes a callback run
into this. Let's say it's __delayed__ instead.
Right, good point. I'm clearly still learning about descriptors. :-)
I'm having a LOT of trouble seeing this as an improvement.
It's not meant to be an improvement exactly, more of a compatible explanation
of how PEP 671 works -- in the same way that `instance.method` doesn't
"magically" make a bound method, but rather checks whether `instance.method`
has a `__get__` attribute, and if so, calls it with `instance` as an
argument, instead of returning `instance.method` directly. This mechanism
makes the whole `instance.method` less magic, more introspectable, more
overridable, etc., e.g. making classmethod and similar decorators possible.
I'm trying to do the same thing with PEP 671 (though possibly failing :-)).
At least it's still executing the function in its natural scope; it's
"just" the locals() dict that gets exposed, as an argument.
Yes, which means you can't access nonlocals or globals, only locals.
So it has a subset of functionality in an awkward way.
My actual intent was to just be able to access the arguments, which are all
locals to the function. [Conceptually, I was thinking of the arguments being
in their own object, and then getting accessed once like attributes, which
triggered __get__ if defined -- but this view isn't very good, in particular
because we don't want to redefine what it means to pass functions as
arguments!]
But the __delayed__ method is already a function, so it has its own locals,
nonlocals, and globals. The difference is that those are in the frame of
__delayed__, which is outside the function with the defaults, and I wanted to
access that function's arguments -- hence passing in the function's locals().
Alternatively, we could forbid this (at least for now): perhaps a __get__
method only gets checked and called on a parameter when that parameter has its
default value (e.g. `end is bisect.__defaults__[1]`).
That part's not a problem; if this has language support, it could be
much more explicit: "if the end parameter was not set".
True. I was trying to preserve the "skip this argument" property, but it
might make more sense to call __delayed__ only when the argument is omitted.
This might make it possible for defaults with __delayed__ methods to actually
be evaluated in the function's scope, which would make it more compatible with
the current PEP 671.
AND it becomes impossible to have an object with this
method as an early default - that's the sentinel problem.
That's true. I guess my point is that these *are* early defaults, but act
very much like late defaults. Functions or function calls just treat these
early defaults specially because they have a __delayed__ method.
I agree it's not perfect, but is there a context where you'd actually want to
have an early default that is one of these objects? The point to add a method
to an early default that makes the early default behave like a late default.
So this feels like expected behavior...?
The use of locals() (as an argument to __get__) is rather ugly, and probably
prevents name lookup optimization.
Yes. It also prevents use of anything other than locals. For instance,
you can't have global helper functions, or anything like that; you
could use something like len() from the builtins, but you couldn't use
a function defined in the same module. Passing both globals and locals
would be better, but still imperfect; and it incurs double lookups
every time.
That wasn't my intent. The __delayed__ method is still a function, and has
its own locals, nonlocals, and globals. It can still call len (as my example
code did) -- it's just the len visible from the __delayed__ function, not the
len visible from the function with the default parameter.
It's true that this approach would prevent implementing something like this:
```
def foo(a => (b := 5)):
nonlocal b
```
I'm not sure that that is particularly important: I just wanted the default
expression to be able to access the arguments and the surrounding scopes.
Sure. Explore anything you like! But I don't think that this is any less
ugly than either the status quo or PEP 671, both of which involve actual
real code being parsed by the compiler.
This proposal was meant to help define what the compiler with PEP 671 parsed
code *into*.
Erik
--
Erik Demaine | edema...@mit.edu | http://erikdemaine.org/
_______________________________________________
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/XEAQUHZPOIHZJKY5NPBUKRNC4MNJOWDX/
Code of Conduct: http://python.org/psf/codeofconduct/