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 haveclass 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 anOrderedDict
containing all instances ofExampleDecorator
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 listsOrderedDescriptor
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 whatOrderedDescriptorContainer
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_
fromPoint2D
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 differentOrderedDescriptor
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.