Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

preserving the order of Traitlets as defined in the class #148

Closed
perrygreenfield opened this issue Dec 16, 2015 · 11 comments
Closed

preserving the order of Traitlets as defined in the class #148

perrygreenfield opened this issue Dec 16, 2015 · 11 comments

Comments

@perrygreenfield
Copy link

For our application, it would be extremely useful to retain the order of the traits as defined internally. I.e., we would like to determine the order the traits were defined in the class. Any way of supporting this other than our making our own modified copy of traitlets? (The motivation for this is so that tools that can print out or otherwise save this information in a readable state can retain logical groupings of traits as defined in the class).

@rmorshea
Copy link
Contributor

I may be wrong, but I have a feeling this isn't likely to be something that would be included in vanilla Traitlets. However, #131 concerns generating a docstring in __init__ about the traits on the instance. The information is simply ordered alphabetically, but maybe it helps with readability at least.

@ssanderson
Copy link
Member

This could be done very easily in Python 3 by implementing a prepare on MetaHasTraits that returns an OrderedDict. In Python 2 I don't think there's a straightforward way to do this.

On Dec 16, 2015, at 2:42 PM, perrygreenfield [email protected] wrote:

For our application, it would be extremely useful to retain the order of the traits as defined internally. I.e., we would like to determine the order the traits were defined in the class. Any way of supporting this other than our making our own modified copy of traitlets? (The motivation for this is so that tools that can print out or otherwise save this information in a readable state can retain logical groupings of traits as defined in the class).


Reply to this email directly or view it on GitHub.

@perrygreenfield
Copy link
Author

At the moment we are trying to support both 2 and 3 so that may be an issue, at least for a couple years. Alphabetical is easy to do, but isn't usually what the user wants. They usually want to see related parameters close together, and typically more frequently changed parameters higher in the list. All this relates to our need to serialize and de-serialize the parameter information to and from files that users want to view and edit (this is a very common interface in astronomy for running applications).

@ssanderson
Copy link
Member

If you want to support Py2 then you'd have to construct your traitlets ahead of time and then dynamically add them to the body of the class, ala:

_myclass_traits = [('dict_trait', Dict(...)), ('list_trait', List(...))]

class MyTraitedClass(HasTraits):
    _trait_order = []
    ls = locals()  # fun Python trivia: locals() is mutable in the body of a class.
    for name, value in _myclass_traits:
        ls[name] = value
        _trait_order.append(value)

# MyTraitedClass now has all the desired traits, plus a list of them in order as a class-level attribute named _trait_order.

This might be a reasonable thing to do if you're defining all your traited classes ahead of time, and you don't expect users to have to extend your traited classes.

If you're expecting your users to be participants in the class hierarchy I expect that it wouldn't be with the added confusion and complexity.

@rmorshea
Copy link
Contributor

How do you plan on controlling order with traits inherited from other classes? It seems like you need a system of priority and grouping that is independent of the defined order of traits on any particular class to resolve that. I think your best bet is to just tag the trait with some metadata doc_order=<n>. Then you can just run through the mro, group traits with the same doc_order on a class together, and then order those groups.

@perrygreenfield
Copy link
Author

Adding a ordering keyword is pretty clumsy and I don't think it would be acceptable. Well, there's always the source code to self analyze :-)

@rmorshea
Copy link
Contributor

It could also be a dictionary defined on the class which is keyed on trait names, and whose values are their order. Then you could run through that dictionary and tag them in the metaclass instead of doing so in the class definition.

@perrygreenfield
Copy link
Author

Any idea of how much work to add an alternate class HasOrderedTraits?

@ssanderson
Copy link
Member

@perrygreenfield in Python 3, it's pretty trivial. As an example, here's an implementation that provides an ordered_traits attribute on subclasses of OrderedHasTraits, and a trait_values property on instances of those subclasses, both of which contain OrderedDicts mapping names to trait instances, and trait values, respectively:

from collections import OrderedDict
from traitlets import MetaHasTraits, HasTraits, List, Dict, is_trait

from ipython_genutils.py3compat import iteritems, with_metaclass


class OrderedMetaHasTraits(MetaHasTraits):

    def __prepare__(name, bases, **kwds):
        return OrderedDict()

    def __init__(cls, name, bases, classdict):
        """Set up the `ordered_traits` attribute on subtypes of OrderedHasTraits."""
        super(OrderedMetaHasTraits, cls).__init__(name, bases, classdict)
        cls.ordered_traits = OrderedDict(
            (k, v) for k, v in iteritems(classdict)
            if is_trait(v)
        )


class OrderedHasTraits(with_metaclass(OrderedMetaHasTraits, HasTraits)):

    @property
    def trait_values(self):
        return OrderedDict((key, getattr(self, key)) for key in type(self).ordered_traits)


if __name__ == '__main__':
    class MyType(OrderedHasTraits):
        x = List()
        y = Dict()

    print(MyType.ordered_traits)
    print(MyType(x=[], y={}).trait_values)

Running that script produces this as output:

python scratch.py
OrderedDict([('x', <traitlets.traitlets.List object at 0x7f4a2f11a908>), ('y', <traitlets.traitlets.Dict object at 0x7f4a2f127208>)])
OrderedDict([('x', []), ('y', {})])

The only thing that would probably still need some fleshing out here would be merging these attributes in subclasses.

In Python 2, there's no way to accomplish this that I'm aware of, because by the time our metaclass gets access to the class body, it's already a dict with no implied order. If you wanted to replicate this behavior in Python 2, you'd have to require to user to supply an ordering, which seems like the whole thing you're trying to avoid here.

@perrygreenfield
Copy link
Author

Thanks for taking the time to explain. It saved me quite a bit of time trying to distill that aspect.

@rmorshea
Copy link
Contributor

rmorshea commented Apr 4, 2019

Closing given that support for Python 2 ends in 2020, the solution proposed by @ssanderson for Python<3.6, and that this is no longer an issue in Python>=3.6 due to PEP-520.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants