> On 1 Dec 2017, at 12:29, Nick Coghlan <[email protected]> wrote:
>
> On 1 December 2017 at 21:04, Ronald Oussoren <[email protected]> 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
[email protected]
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/