astro py:docs

- Index
- Modules

### Navigation

- next »
- « previous |
- Astropy v1.1.1 »
- Astropy Core Package Utilities (`astropy.utils`) »
- OrderedDescriptorContainer

# OrderedDescriptorContainer

class `astropy.utils.misc.``OrderedDescriptorContainer`(cls_name, bases, members)

  Bases: `type`

  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.extern import six
      >>> 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(TypedAttribute, self).__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`:

      >>> @six.add_metaclass(OrderedDescriptorContainer)
      ... class Point2D(object):
      ...     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.