PEP 447: Add __getdescriptor__ to metaclasses

Hi, It’s getting a tradition for me to work on PEP 447 during the EuroPython sprints and disappear afterwards. Hopefully I can manage to avoid the latter step this year… Last year the conclusion appeared to be that this is an acceptable PEP, but Mark Shannon had a concern about a default implementation for __getdescriptor__ on type in <https://mail.python.org/pipermail/python-dev/2015-July/140938.html> (follow the link for more context):
The implementation of the PEP in issue 18181 <http://bugs.python.org/issue18181> does special-case type.__getdescriptor__ but only as an optimisation, the code would work just as well without that special casing because the normal attribute lookup machinery is not used when accessing special methods written in C. That is, the implementation of object.__getattribute__ directly accesses fields of the type struct at the C level. Some magic behavior appears to be necessary even without the addition of __getdescriptor__ (type is a subclass of itself, object.__getattribute__ has direct access to dict.__getitem__, …). I’m currently working on getting the patch in 18181 up-to-date w.r.t. the current trunk, the patch in the issue no longer applies cleanly. After that I’ll try to think up some tests that seriously try to break the new behaviour, and I want to update a patch I have for PyObjC to make use of the new functionality to make sure that the PEP actually fixes the issues I had w.r.t. builtin.super’s behavior. What is the best way forward after that? As before this is a change in behavior that, unsurprisingly, few core devs appear to be comfortable with evaluating, combined with new functionality that will likely see little use beyond PyObjC (although my opinions of that shouldn’t carry much weight, I thought that decorators would have limited appeal when those where introduced and couldn’t have been more wrong about that). Ronald P.S. The PEP itself: <https://www.python.org/dev/peps/pep-0447>

On 23 July 2016 at 22:26, Ronald Oussoren <ronaldoussoren@mac.com> wrote:
You may also want to check compatibility with Martin's patch for PEP 487 (__init_subclass__ and __set_name__) at http://bugs.python.org/issue27366 I don't *think* it will conflict, but "try it and see what happens" is generally a better idea for the descriptor machinery than assuming changes are going to be non-conflicting :)
You may want to explicitly ping the https://github.com/ipython/traitlets developers to see if this change would let them do anything they currently find impractical or impossible. As far as Mark's concern about a non-terminating method definition goes, I do think you need to double check how the semantics of object.__getattribute__ are formally defined. >>> class Meta(type): ... def __getattribute__(self, attr): ... print("Via metaclass!") ... return super().__getattribute__(attr) ... >>> class Example(metaclass=Meta): pass ... >>> Example.mro() Via metaclass! [<class '__main__.Example'>, <class 'object'>] Where the current PEP risks falling into unbounded recursion is that it appears to propose that the default type.__getdescriptor__ implementation be defined in terms of accessing cls.__dict__, but a normal Python level access to "cls.__dict__" would go through the descriptor machinery, triggering an infinite regress. The PEP needs to be explicit that where "cls.__dict__" is written in the definitions of both the old and new lookup semantics, it is *not* referring to a normal class attribute lookup, but rather to the interpreter's privileged access to the class namespace (e.g. direct 'tp_dict' access in CPython). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

I also don’t think the two will conflict, but that’s based on a superficial read of that PEP the last time it was posted on python-dev. PEP 487 and 447 affect different parts of the object model, in particular PEP 487 doesn’t affect attribute lookup.
I’ll ask them.
On first glance the same is true for all access to dunder attributes in sample code for the PEP, a similar example could be written for __get__ or __set__. I have to think a bit more about how to clearly describe this. I’m currently coaxing PyObjC into using PEP 447 when that’s available and that involves several layers of metaclasses in C and that’s annoyingly hard to debug when the code doesn’t do what I want like it does now. But on the other hand, that’s why wanted to use PyObjC to validate the PEP in the first place. Back to wrangling C code, Ronald

On 24 Jul 2016, at 13:06, Ronald Oussoren <ronaldoussoren@mac.com <mailto:ronaldoussoren@mac.com>> wrote:
…
But on the other hand, that’s why wanted to use PyObjC to validate the PEP in the first place.
I’ve hit a fairly significant issue with this, PyObjC’s super contains more magic than just this magic that would be fixed by PEP 447. I don’t think I’ll be able to finish work on PEP 447 this week because of that, and in the worst case will have to retire the PEP. The problem is as follows: to be able to map all of Cocoa’s methods to Python PyObjC creates two proxy classes for every Cocoa class: the regular class and its metaclass. The latter is used to store class methods. This is needed because Objective-C classes can have instance and class methods with the same name, as an example: @interface NSObject -(NSString*)description; +(NSString*)description @end The first declaration for “description” is an instance method, the second is a class method. The Python metaclass is mostly a hidden detail, users don’t explicitly interact with these classes and use the normal Python convention for defining class methods. This works fine, problems starts when you want to subclass in Python and override the class method: class MyClass (NSObject): @classmethod def description(cls): return “hello there from %r” % (super(MyClass, cls).description()) If you’re used to normal Python code there’s nothing wrong here, but getting this to work required some magic in objc.super to ensure that its __getattribute__ looks in the metaclass in this case and not the regular class. The current PEP447-ised version of PyObjC has a number of test failures because builtin.super obviously doesn’t contain this hack (and shouldn’t). I think I can fix this for modern code that uses an argumentless call to super by replacing the cell containing the __class__ reference when moving the method from the regular class to the instance class. That would obviously not work for the code I showed earlier, but that at least won’t fail silently and the error message is specific enough that I can include it in PyObjC’s documentation. Ronald

Hi Ronald, The feature freeze for 3.6 is closing in a few days; 3.6b1 will go out this weekend. Did you overcome the issue, or does your PEP need to be postponed until 3.7? --Guido On Sun, Jul 24, 2016 at 9:58 PM, Ronald Oussoren <ronaldoussoren@mac.com> wrote:
-- --Guido van Rossum (python.org/~guido)

On 23 July 2016 at 22:26, Ronald Oussoren <ronaldoussoren@mac.com> wrote:
You may also want to check compatibility with Martin's patch for PEP 487 (__init_subclass__ and __set_name__) at http://bugs.python.org/issue27366 I don't *think* it will conflict, but "try it and see what happens" is generally a better idea for the descriptor machinery than assuming changes are going to be non-conflicting :)
You may want to explicitly ping the https://github.com/ipython/traitlets developers to see if this change would let them do anything they currently find impractical or impossible. As far as Mark's concern about a non-terminating method definition goes, I do think you need to double check how the semantics of object.__getattribute__ are formally defined. >>> class Meta(type): ... def __getattribute__(self, attr): ... print("Via metaclass!") ... return super().__getattribute__(attr) ... >>> class Example(metaclass=Meta): pass ... >>> Example.mro() Via metaclass! [<class '__main__.Example'>, <class 'object'>] Where the current PEP risks falling into unbounded recursion is that it appears to propose that the default type.__getdescriptor__ implementation be defined in terms of accessing cls.__dict__, but a normal Python level access to "cls.__dict__" would go through the descriptor machinery, triggering an infinite regress. The PEP needs to be explicit that where "cls.__dict__" is written in the definitions of both the old and new lookup semantics, it is *not* referring to a normal class attribute lookup, but rather to the interpreter's privileged access to the class namespace (e.g. direct 'tp_dict' access in CPython). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

I also don’t think the two will conflict, but that’s based on a superficial read of that PEP the last time it was posted on python-dev. PEP 487 and 447 affect different parts of the object model, in particular PEP 487 doesn’t affect attribute lookup.
I’ll ask them.
On first glance the same is true for all access to dunder attributes in sample code for the PEP, a similar example could be written for __get__ or __set__. I have to think a bit more about how to clearly describe this. I’m currently coaxing PyObjC into using PEP 447 when that’s available and that involves several layers of metaclasses in C and that’s annoyingly hard to debug when the code doesn’t do what I want like it does now. But on the other hand, that’s why wanted to use PyObjC to validate the PEP in the first place. Back to wrangling C code, Ronald

On 24 Jul 2016, at 13:06, Ronald Oussoren <ronaldoussoren@mac.com <mailto:ronaldoussoren@mac.com>> wrote:
…
But on the other hand, that’s why wanted to use PyObjC to validate the PEP in the first place.
I’ve hit a fairly significant issue with this, PyObjC’s super contains more magic than just this magic that would be fixed by PEP 447. I don’t think I’ll be able to finish work on PEP 447 this week because of that, and in the worst case will have to retire the PEP. The problem is as follows: to be able to map all of Cocoa’s methods to Python PyObjC creates two proxy classes for every Cocoa class: the regular class and its metaclass. The latter is used to store class methods. This is needed because Objective-C classes can have instance and class methods with the same name, as an example: @interface NSObject -(NSString*)description; +(NSString*)description @end The first declaration for “description” is an instance method, the second is a class method. The Python metaclass is mostly a hidden detail, users don’t explicitly interact with these classes and use the normal Python convention for defining class methods. This works fine, problems starts when you want to subclass in Python and override the class method: class MyClass (NSObject): @classmethod def description(cls): return “hello there from %r” % (super(MyClass, cls).description()) If you’re used to normal Python code there’s nothing wrong here, but getting this to work required some magic in objc.super to ensure that its __getattribute__ looks in the metaclass in this case and not the regular class. The current PEP447-ised version of PyObjC has a number of test failures because builtin.super obviously doesn’t contain this hack (and shouldn’t). I think I can fix this for modern code that uses an argumentless call to super by replacing the cell containing the __class__ reference when moving the method from the regular class to the instance class. That would obviously not work for the code I showed earlier, but that at least won’t fail silently and the error message is specific enough that I can include it in PyObjC’s documentation. Ronald

Hi Ronald, The feature freeze for 3.6 is closing in a few days; 3.6b1 will go out this weekend. Did you overcome the issue, or does your PEP need to be postponed until 3.7? --Guido On Sun, Jul 24, 2016 at 9:58 PM, Ronald Oussoren <ronaldoussoren@mac.com> wrote:
-- --Guido van Rossum (python.org/~guido)
participants (4)
-
Brett Cannon
-
Guido van Rossum
-
Nick Coghlan
-
Ronald Oussoren