OrderedDescriptorContainer

class astropy.utils.misc.OrderedDescriptorContainer(cls_name, bases, members)[source]

Bases: type

Deprecated since version 4.3: The OrderedDescriptorContainer class is deprecated and may be removed in a future version.

You can replace its functionality with a combination of the __init_subclass__ and __set_name__ magic methods introduced in Python 3.6. See https://github.com/astropy/astropy/issues/11094 for recipes on how to replicate their functionality.

Classes should use this metaclass if they wish to use OrderedDescriptor attributes, which are class attributes that “remember” the order in which they were defined in the class body.

Every subclass of OrderedDescriptor has an attribute called _class_attribute_. For example, if we have

class ExampleDecorator(OrderedDescriptor):
    _class_attribute_ = '_examples_'

Then when a class with the OrderedDescriptorContainer metaclass is created, it will automatically be assigned a class attribute _examples_ referencing an OrderedDict containing all instances of ExampleDecorator defined in the class body, mapped to by the names of the attributes they were assigned to.

When subclassing a class with this metaclass, the descriptor dict (i.e. _examples_ in the above example) will not contain descriptors inherited from the base class. That is, this only works by default with decorators explicitly defined in the class body. However, the subclass may define an attribute _inherit_decorators_ which lists OrderedDescriptor classes that should be added from base classes. See the examples section below for an example of this.

Examples

>>> from astropy.utils import OrderedDescriptor, OrderedDescriptorContainer
>>> class TypedAttribute(OrderedDescriptor):
...     """
...     Attributes that may only be assigned objects of a specific type,
...     or subclasses thereof.  For some reason we care about their order.
...     """
...
...     _class_attribute_ = 'typed_attributes'
...     _name_attribute_ = 'name'
...     # A default name so that instances not attached to a class can
...     # still be repr'd; useful for debugging
...     name = '<unbound>'
...
...     def __init__(self, type):
...         # Make sure not to forget to call the super __init__
...         super().__init__()
...         self.type = type
...
...     def __get__(self, obj, objtype=None):
...         if obj is None:
...             return self
...         if self.name in obj.__dict__:
...             return obj.__dict__[self.name]
...         else:
...             raise AttributeError(self.name)
...
...     def __set__(self, obj, value):
...         if not isinstance(value, self.type):
...             raise ValueError('{0}.{1} must be of type {2!r}'.format(
...                 obj.__class__.__name__, self.name, self.type))
...         obj.__dict__[self.name] = value
...
...     def __delete__(self, obj):
...         if self.name in obj.__dict__:
...             del obj.__dict__[self.name]
...         else:
...             raise AttributeError(self.name)
...
...     def __repr__(self):
...         if isinstance(self.type, tuple) and len(self.type) > 1:
...             typestr = '({0})'.format(
...                 ', '.join(t.__name__ for t in self.type))
...         else:
...             typestr = self.type.__name__
...         return '<{0}(name={1}, type={2})>'.format(
...                 self.__class__.__name__, self.name, typestr)
...

Now let’s create an example class that uses this TypedAttribute:

>>> class Point2D(metaclass=OrderedDescriptorContainer):
...     x = TypedAttribute((float, int))
...     y = TypedAttribute((float, int))
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
>>> p1 = Point2D(1.0, 2.0)
>>> p1.x
1.0
>>> p1.y
2.0
>>> p2 = Point2D('a', 'b')  
Traceback (most recent call last):
    ...
ValueError: Point2D.x must be of type (float, int>)

We see that TypedAttribute works more or less as advertised, but there’s nothing special about that. Let’s see what OrderedDescriptorContainer did for us:

>>> Point2D.typed_attributes
OrderedDict([('x', <TypedAttribute(name=x, type=(float, int))>),
('y', <TypedAttribute(name=y, type=(float, int))>)])

If we create a subclass, it does not by default add inherited descriptors to typed_attributes:

>>> class Point3D(Point2D):
...     z = TypedAttribute((float, int))
...
>>> Point3D.typed_attributes
OrderedDict([('z', <TypedAttribute(name=z, type=(float, int))>)])

However, if we specify _inherit_descriptors_ from Point2D then it will do so:

>>> class Point3D(Point2D):
...     _inherit_descriptors_ = (TypedAttribute,)
...     z = TypedAttribute((float, int))
...
>>> Point3D.typed_attributes
OrderedDict([('x', <TypedAttribute(name=x, type=(float, int))>),
('y', <TypedAttribute(name=y, type=(float, int))>),
('z', <TypedAttribute(name=z, type=(float, int))>)])

Note

Hopefully it is clear from these examples that this construction also allows a class of type OrderedDescriptorContainer to use multiple different OrderedDescriptor classes simultaneously.

Deprecated since version 4.3: The OrderedDescriptorContainer class is deprecated and may be removed in a future version.

You can replace its functionality with a combination of the __init_subclass__ and __set_name__ magic methods introduced in Python 3.6. See https://github.com/astropy/astropy/issues/11094 for recipes on how to replicate their functionality.

Create and return a new object. See help(type) for accurate signature.