Repository navigation
Simplify OrderedDescriptor[Container] #11094
Description
The work to remove metaclasses in favour of __init_subclass__ at #11090 reminded me that @embray had mentioned that this would become possible, and that similar changes would also allow one to simplify OrderedDecriptor:
Once Python 3.6 is out I would encourage looking for bits in Astropy
that can be simplified by these PEPs. The most obvious that comes to
mind is the OrderedDescriptor class, and its associated
OrderedDescriptorContainer class (that a class must subclass in order
for OrderedDescriptors to work on it). PEP 520 makes
OrderedDescriptor trivial, and obviates the need for it.
This is a reminder for us to look at this.
Details
The combination of OrderedDescriptor and OrderedDescriptorContainer provided two functionalities:
It allowed setting a "name" attribute on each OrderedDescriptor when the class is defined. This can be replaced with the __set_name__ magic method introduced in Python 3.6 by PEP 487. For example, given:
class ExampleDecorator(OrderedDescriptor):
_name_attribute_ = 'name'this can now be written:
class ExampleDescriptor:
def __set_name__(self, owner, name):
self.name = namewhen defining a class using this descriptor, e.g. like
class MyClass:
descr1 = ExampleDescriptor()
descr2 = ExampleDescriptor()at class definition time __set_name__ will be called on each ExampleDescriptor with the name it was assigned to in the class body, resulting in:
>>> MyClass.descr1.name
'descr1'
>>> MyClass.descr2.name
'descr2'The other functionality of OrderedDescriptor + OrderedDescriptorContainer is that it would set an attribute on the class containing an OrderedDict of all instances of some OrderedDescriptor subclass. For example, given
class ExampleDescriptor(OrderedDescriptor):
_class_attribute_ = '_examples_'this can now be implemented in the class which contains the ExampleDescriptors (without using OrderedDescriptor or OrderedDescriptorContainer at all) by implementing __init_subclass__ on a base class:
class MyBaseClass:
_examples_ = {}
def __init_subclass__(cls):
cls._examples_ = {k, v for k, v in cls.__dict__.items()
if isinstance(v, ExampleDescriptor)}Now use this base class and put some ExampleDescriptors in the class body:
class MyClass(MyBaseClass):
descr1 = ExampleDescriptor()
descr2 = ExampleDescriptor()Resulting in:
>>> MyClass._examples_
{'descr1': <__main__.ExampleDescriptor at 0x7f4e2bfb44e0>,
'descr2': <__main__.ExampleDescriptor at 0x7f4e2bfb4ef0>}Finally, a more advanced feature of OrderedDescriptorContainer was if you set _inherit_descriptors_ = True in classes which used it. This would cause the descriptors in its _class_attribute_ to be inherited from base classes.
If this functionality is needed it can also be achieved with a slightly more advanced __init_subclass__:
class MyBaseClass:
_examples_ = {}
def __init_subclass__(cls):
for basecls in cls.__mro__:
if hasattr(basecls, '_examples_') and isinstance(basecls._examples_, dict):
cls._examples_ = dict(basecls._examples_)
break
else:
cls._examples_ = {}
cls._examples_.update(
{k, v for k, v in cls.__dict__.items()
if isinstance(v, ExampleDescriptor)})Then given
class MyClass(MyBaseClass):
descr1 = ExampleDescriptor()
descr2 = ExampleDescriptor()and
class MySubClass(MyClass):
descr3 = ExampleDescriptor()The result is:
>>> MySubClass._examples_
{'descr1': <__main__.ExampleDescriptor at 0x7f4e2bfb44e0>,
'descr2': <__main__.ExampleDescriptor at 0x7f4e2bfb4ef0>,
'descr3': <__main__.ExampleDescriptor at 0x7f4e2bf9b550>}
Activity
embray commentedon Nov 27, 2020
Cool, thanks for the reminder. Indeed, I believe this is still the case. In fact, class
__dict__s now preserve the order that values are added to them, which means that when class bodies are executed the class__dict__entries will be in the same order as the class body.embray commentedon Nov 27, 2020
Combined also with the
__set_name__hook from PEP 487 I thinkOrderedDescriptorcould even be deprecated/removed entirely, though I would need to do a quick review of how they're actually being used to be sure.[astropy#10888] Motivated in part astropy#11094, this removes all use…
embray commentedon Nov 27, 2020
I'm updating the description of this issue with details on how to replace
OrderedDescriptorin a class, so I can reference this issue in the deprecation message.[astropy#11094] Remove use of OrderedDescriptor for astropy.modeling
[astropy#11094] Remove use of OrderedDescriptor[Container] from
[astropy#11094] Mark OrderedDescriptor and OrderedDescriptorContainer as
[astropy#10888] Motivated in part astropy#11094, this removes all use…
[astropy#11094] Remove use of OrderedDescriptor for astropy.modeling
[astropy#11094] Remove use of OrderedDescriptor[Container] from
[astropy#11094] Mark OrderedDescriptor and OrderedDescriptorContainer as
[astropy#11094][astropy#11090] Remove FrameMeta entirely as it is no …
42 remaining items
[astropy#11094] Remove use of OrderedDescriptor[Container] from
[astropy#11094] Mark OrderedDescriptor and OrderedDescriptorContainer as
[astropy#11094][astropy#11090] Remove FrameMeta entirely as it is no …
[astropy#11094] Remove use of OrderedDescriptorContainer in the tests…
[astropy#11094] Skip OrderedDescriptor doctests.
[astropy#11094] Collect Frame Attributes from all base classes as wel…
[astropy#11094] Add changelog entry. [skip ci]
[astropy#11094] Remove use of OrderedDescriptor for astropy.modeling
[astropy#11094] Remove use of OrderedDescriptor[Container] from
[astropy#11094] Mark OrderedDescriptor and OrderedDescriptorContainer as
[astropy#11094][astropy#11090] Remove FrameMeta entirely as it is no …
[astropy#11094] Remove use of OrderedDescriptorContainer in the tests…
[astropy#11094] Skip OrderedDescriptor doctests.
[astropy#11094] Collect Frame Attributes from all base classes as wel…
[astropy#11094] Add changelog entry. [skip ci]