[Python-ideas] PEP 447: Adding type.__getdescriptor__

Nick Coghlan ncoghlan at gmail.com
Sat Dec 2 21:58:02 EST 2017


On 2 December 2017 at 01:12, Ronald Oussoren <ronaldoussoren at mac.com> wrote:
> On 1 Dec 2017, at 12:29, Nick Coghlan <ncoghlan at gmail.com> wrote:
>> 2. Define it as a class method, but have the convention be for the
>> *caller* to worry about walking the MRO, and hence advise class
>> implementors to *never* call super() from __getdescriptor__
>> implementations (since doing so would nest MRO walks, and hence
>> inevitably have weird outcomes). Emphasise this convention by passing
>> the current base class from the MRO as the second argument to the
>> method.
>
> But that’s how I already define the method, that is the PEP proposes to change
> the MRO walking loop to:
>
>    for cls in mro_list:
>         try:
>             return cls.__getdescriptor__(name)  # was cls.__dict__[name]
>         except AttributeError:                  # was KeyError
>             pass
>
> Note that classes on the MRO control how to try to fetch the name at that level. The code is the same for __getdescriptor__ as a classmethod and as a method on the  metaclass.

That's not exactly the same as what I'm suggesting, and it's the part
that has Mark concerned about an infinite regression due to the
"cls.__getdescriptor__" subexpression.

What I'm suggesting:

    try:
        getdesc = base.__dict__["__getdescriptor__"]
    except KeyError:
        # Use existing logic
    else:
        try:
            getdesc(cls, base, name)
        except AttributeError:
            pass

* Neither type nor object implement __getdescriptor__
* Calling super() in __getdescriptor__ would be actively discouraged
without a base class to define the cooperation rules
* If it's missing in the base class dict, fall back to checking the
base class dict directly for the requested attribute
* cls is injected into the call by the MRO walking code *not* the
normal bound method machinery
* Only "base.__dict__" needs to be assured of getting a hit on every base class

What's currently in the PEP doesn't clearly define how it thinks
"cls.__getdescriptor__" should work without getting itself into an
infinite loop.

> I don’t think there’s a good technical reason to pick either option, other than that the metaclass option forces an exception when creating a class that inherits (using multiple inheritance) from two classes that have a custom __getdescriptor__. I’m not convinced that this is a good enough reason to go for the metaclass option.

I'm still not clear on how you're planning to break the recursive loop
for "cls.__getdescriptor__" when using a metaclass.

>> The reason I'm liking option 2 is that it leaves the existing
>> __getattribute__ implementations fully in charge of the MRO walk, and
>> *only* offers a way to override the "base.__dict__[name]"  part with a
>> call to "base.__dict__['__getdescriptor__'](cls, base, name)" instead.
>
> Right. That’s why I propose __getdescriptor__ in the first place. This allows Python coders to do extra work (or different work) to fetch an attribute of a specific class in the MRO and works both with regular attribute lookup as well as lookup through super().

Yeah, I'm definitely in agreement with the intent of the PEP. I'm just
thinking that we should aim to keep the expected semantics of the hook
as close as we can to the semantics of the code it's replacing, rather
than risking the introduction of potentially nested MRO walks (we
can't outright *prevent* the latter, but we *can* quite clearly say
"That's almost certainly a bad idea, avoid it if you possibly can").

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list