Skip to content

OrderedDescriptor and OrderedDescriptorContainer - #3679

Merged
embray merged 7 commits into
astropy:masterfrom
embray:utils/ordered-descr
Aug 19, 2015
Merged

embray merged 7 commits into
astropy:masterfrom
embray:utils/ordered-descr

Conversation

@embray

@embray embray commented Apr 10, 2015

Copy link
Copy Markdown
Member

This PR implements two new classes that provide a generic implementation of a pattern that has come up in a couple places, and I anticipate using again. The pattern is having a class that implements some descriptor, which will be instantiated multiple times at the level of a class declaration, and where we want to be able to record the order in which those descriptors were created in the class definition.

The two examples of this in Astropy currently, are Model, which uses the Parameter descriptor to declare parameters of a model, and the FrameAttribute class used with coordinate frames in the coordinates package.

I thought of trying to generalize this pattern because I recently started writing yet another class that worked exactly the same way, for a modeling update. It's worth noting that in Python 3 the __prepare__ magic method mostly supplants this pattern. But there are some advantages to OrderedDescriptor + OrderedDescriptorContainer over __prepare__, such as automatic assignment to a .name attribute on the descriptors based on the name of the attribute they were assigned to. I think inheritance/subclassing is also handled pretty well here.

For more details see the docstrings for OrderedDescriptor and OrderedDescriptorContainer. This PR also updates the modeling package and the coordinates package to use these new classes. Feedback is appreciated.

@embray

embray commented Apr 13, 2015

Copy link
Copy Markdown
Member Author

Any thoughts about this?

@mdboom

mdboom commented Apr 13, 2015

Copy link
Copy Markdown
Contributor

Sorry -- I had meant to have a look at this and haven't had a chance (and this week is awful for me). Don't want to hold up the works, though.

@taldcroft

Copy link
Copy Markdown
Member

Sorry also been busy but I like the idea and simplification in coordinates. Will try for a more detailed look this week.

@embray

embray commented Apr 14, 2015

Copy link
Copy Markdown
Member Author

Ack, it's okay. Now that I'm in it, this week is awful for me too; I don't expect I'll need this 'til after Leiden.

@taldcroft

Copy link
Copy Markdown
Member

I haven't attempted to understand all the details of the new code, but I note that it is extremely well documented and so the subtleties should be transparent to future maintainers!

From the coordinates perspective I've looked through the changes and confirmed that things look reasonable. Given that tests are passing I'm 👍 on this. @eteq - do you want to weigh in on this?

@embray

embray commented May 4, 2015

Copy link
Copy Markdown
Member Author

I guess to help guide any further review, there are two main things about this I'm not 100% certain about and would like feedback on (and these can always be changed later if need be). Both have to do with how the Descriptor class hooks up with classes it's used on.

The first is the _class_attribute_ option. This attribute is defined on the Descriptors, but it requires the class using those descriptors to have an attribute as defined by _class_attribute_ which may be seen as a bit incongruous. An alternative might be for the class using the descriptors to specify the name of an attribute that each descriptor class is tied to. To give an example from coordinates, currently we write:

class FrameAttribute(OrderedDescriptor):
    _class_attribute_ = 'frame_attributes'
    # ...

class FrameMeta(OrderedDescriptorContainer):
    #...

class BaseFrame(metaclass=FrameMeta):
    # Default empty frame_attributes; in principle it's not necessary to define
    # this default, but in practice it's pretty convenient to, and makes more clear
    # that this attribute exists (otherwise it will come in 'magically' via the metaclass
    frame_attributes = OrderedDescriptor()

This of course means that if I wanted to use FrameAttribute in another class, but I wanted it use a different class attribute than frame_attributes, I'd have to subclass FrameAttribute and override the _class_attribute_. A don't think that's a huge problem myself, but I can see how one might find it confusing. An alternative would be to specify this mapping in the class that uses the descriptors. For example it could specify up front which OrderedDescriptors it wants to collect. There would be no FrameAttribute._class_attribute_, and instead:

class BaseFrame(metaclass=FrameMeta):
    _ordered_descriptors_ = {FrameAttribute: 'frame_attributes'}
    frame_attributes = OrderedDict()

etc. This would map ordered descriptor classes to the names of attributes on the class. It might also be able to automatically set up the default, so that the last line in the above example is unnecessary.

The other question is what the default behavior should be for inheritance. I chose, as the default, for subclasses to not inherit OrderedDescriptor collections from their parent classes, since this is the (current) behavior of Model. But I had to add an option for a different inheritance mechanism to support the existing behavior of BaseFrame. I don't really care which is the default--I think whichever makes more sense.

@taldcroft

Copy link
Copy Markdown
Member

@embray

On the first question, I prefer the current implementation over the option of something like _ordered_descriptors_ = {FrameAttribute: 'frame_attributes'}. The current version seems more obvious to me.

On the second question, my natural inclination would be for the ordered descriptor collections to inherit from their parent classes, but it's not a huge deal either way.

@embray

embray commented May 14, 2015

Copy link
Copy Markdown
Member Author

@eteq As a stakeholder in the coordinates package do you have any opinion on this (or other stakeholders in coordinates?)

I think overall the changes to coordinates in this are very minimal, and mostly result in code simplification. As far as I can tell the behavior of frame attributes is still correct.

@embray

embray commented Jul 7, 2015

Copy link
Copy Markdown
Member Author

Rebased on master. Going to go ahead and merge this soon, after I add a little more documentation.

@taldcroft

Copy link
Copy Markdown
Member

👍

embray referenced this pull request in wkerzendorf/extract_spectral Jul 30, 2015
embray added 6 commits August 4, 2015 11:28
…ainer and OrderedParameter, respectively. This required removing a bunch of code that checked for consistency between the param_names attribute and the names of Parameter attributes. However, I think these checks are mostly moot at this point, and were needed more for transitioning of the interface between versions 0.3 and 0.4 and up. I think with any newer code it's not an issue (and even with older code this was a check against developer error, not user error). To be clear, I updated the documentation. Also had to remove one test that was no longer relevant.
…scriptorContainer for FrameAttribute and BaseFrame. This *mostly* obviates the need for BaseFrame.get_frame_attr_names(). However, the frame_attributes dict created by OrderedDescriptorContainer is different, as it maps to the actual descriptor objects themselves (retrieved from BaseFrame.__dict__ rather than getattr(BaseFrame, attr_name)). There is a lot of code in the coordinates package that assumes the latter behavior, so for now I left BaseFrame.get_frame_attr_names() in place, and just adapted it to use the new interface.
…y/astropy-helpers#158, which is needed for the docs to build correctly.  Also fixed several docstrings that weren't being handled correctly by Sphinx due to use of the InheritDocstrings metaclass (not exactly sure why it wasn't a problem previously though...).
@embray

embray commented Aug 4, 2015

Copy link
Copy Markdown
Member Author

Rebased this again--going to go ahead and merge soon as I get the green light from CI.

I hoped that this would improve some of the problems with subclassing models, but it's still tricky due to deeper reasons (though it will help). I'll go ahead with this as is and address the model subclassing problems separately (#4035).

@embray embray added this to the v1.1.0 milestone Aug 19, 2015
embray added a commit that referenced this pull request Aug 19, 2015
OrderedDescriptor and OrderedDescriptorContainer
@embray
embray merged commit c1030c3 into astropy:master Aug 19, 2015
@embray
embray deleted the utils/ordered-descr branch August 19, 2015 18:42
dhomeier pushed a commit to dhomeier/astropy that referenced this pull request Dec 17, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants