> On 1 Dec 2017, at 12:29, Nick Coghlan <ncogh...@gmail.com> wrote:
> 
> On 1 December 2017 at 21:04, Ronald Oussoren <ronaldousso...@mac.com> wrote:
>> The second question is more a design question: what’s the better design, 
>> having __getdescriptor__ as a class method on classes or as method on 
>> metaclasses? Either one would work, but a class method appears to be easier 
>> to use and with the introduction of __init_subclass__ there is a precedent 
>> for going for a class method.
>> 
>> The  current PEP claims that a method on a metaclass would be better to 
>> avoid subtle problems, but ignores the conceptual cost of adding a 
>> metaclass. The subtle problem is that a class can have two direct 
>> superclasses with a __getdescriptor__ when using multiple inheritance, but 
>> that can already be an issue for other methods and that currently includes 
>> __getattribute__ for most of not all usecases where __getdescriptor__ would 
>> be useful.
> 
> I think it's having it being a method on the metaclass that creates
> the infinite regress Mark was worried about: since type's metaclass
> *is* type, if "__getdescriptor__" is looked up as a regular descriptor
> in its own right, then there's no base case to terminate the recursive
> lookup.

But type.__getattribute__ is already special, it cannot be the same as its 
superclass implementation (because that’s object), and object.__getattribute__ 
logically uses type.__getattribute__ to get at type.__dict__. Adding 
__getdescriptor__ in the mix makes type.__getattribute__ a bit messier, but not 
by much.

> 
> By contrast, defining it as a class method opens up two options:
> 
> 1. Truly define it as a class method, and expect implementors to call
> super().__getdescriptor__() if their own lookup fails. I think this
> will be problematic and a good way to get the kinds of subtle problems
> that prompted you to initially opt for the metaclass method.

The only subtle problem is having a class using multiple inheritance that uses
two __getdescriptor__ implementations from two superclasses, where both
do something beyond looking up the name in __dict__. 

Failing to call super().__getdescriptor__() is similar to failing to do so for
other methods. 

> 
> 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.

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.

> 
> 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(). 

The alternative I came up with before writing the PEP is to add a special API 
that can be used by super(), but that leads to more code duplication as coders 
would have to implement both __getattribute__ and this other method.

I guess another options would be a method that does the work including walking 
the MRO, but that leads to more boilerplate for users of the API. 

BTW. I haven’t had a lot of time to work on the implementation. The code in 
typeobject.c has changed enough that this needs more work than tweaking the 
patch until it applies cleanly.

Ronald

_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to