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/

Reply via email to