Ordering enum value in python

12,357

Solution 1

Your Enum loses the ordering in 3 places. First the attributes on the class body are stored in a dictionary, then you copy the items into another dictionary. Finally your values() returns a 3rd dictionary. A dictionary does not save ordering, and it is impossible to get the ordering of the attributes within the class body.

With this system the easiest is to have a variable

__ordering__ = [ 'single', 'double' ]

And make the values() return a list of tuples (like dict.items()).

class EnumMeta(type):
    def __new__(typ, name, bases, attrs):
        cls_attrs = {}
        cls_choices = {}

        for attr_name, value in attrs.items():
            cls_attrs[attr_name] = attr_name.lower()
            if not attr_name.startswith("__"):
                cls_choices[attr_name.lower()] = value

        ordering = attrs.get('__ordering__')
        if ordering == None:
            ordering = sorted(cls_choices.keys())

        def choices(cls):
            return dict(cls_choices)

        def values(cls, value=None):
            if value is None:
                return [ (k, cls_choices[k] ) for k in ordering ]
            elif not isinstance(value, basestring):
                return [ (k, cls_choices[k] ) for k in value ]
            else:
                return unicode(cls_choices.get(value))

        def keys(cls, nil=False):
            items = list(ordering)
            if nil:
                items.append('')

            return items

        def combined_length(cls):
            return len(",".join(cls.values().keys()))

        def max_length(cls):
            return max(map(len, cls.values().keys()))

        cls_attrs['choices'] = classmethod(choices)
        cls_attrs['values'] = classmethod(values)
        cls_attrs['keys'] = classmethod(keys)
        cls_attrs['combined_length'] = classmethod(combined_length)
        cls_attrs['max_length'] = classmethod(max_length)

        return type(name, bases, cls_attrs)

class SideHemType:
    __ordering__ = ['double', 'single']
    __metaclass__ = EnumMeta

    Single = "Single side hem for opaque fabrics"
    Double = "Double side hem for transparent fabrics"


print SideHemType.keys()
print SideHemType.values()

Solution 2

If you are using Python3.4 you can use the new enum.Enum type, which remembers the order the enum members are declared in.

If you are using an earlier Python, you should use the enum34 package available from PyPI, which supports Pythons back to 2.4.

The enum34 package, if used in Python3, also remembers the order of member declarations. If used in Python 2 it supports an extra _order_ attribute:

from enum import Enum

class SideHemType(Enum):

    _order_ = 'Single Double'  # only needed in Python 2

    Single = "Single side hem for opaque fabrics"
    Double = "Double side hem for transparent fabrics"

    @classmethod
    def combined_length(cls):
        return len(",".join(mbr.name for mbr in cls))

    @classmethod
    def max_length(cls):
        return max(map(len, (mbr.name for mbr in cls)))


print list(SideHemType)  # [SideHemType.Single, SideHemType.Double]

print SideHemType.Double.value  # "Double side hem for transparent fabrics"

Solution 3

Use IntEnum from the enum package and use the integer values to specify the order that you want:

class Shape(IntEnum):
    CIRCLE = 1
    SQUARE = 2
Shape.CIRCLE < Shape.SQUARE

Prints True.

Share:
12,357

Related videos on Youtube

Robert
Author by

Robert

Updated on September 14, 2022

Comments

  • Robert
    Robert about 1 year

    I would like to be able to arrange the ordering of Enum. Has somebody suggestions how this can be solved?

    The following Enum meta class is using:

    class EnumMeta(type):
        def __new__(typ, name, bases, attrs):
            cls_attrs = {}
            cls_choices = []
            for attr_name, value in attrs.items():
                cls_attrs[attr_name] = attr_name.lower()
                if not attr_name.startswith("__"):
                    cls_choices.append((attr_name.lower(), value))
    
            def choices(cls):
                return cls_choices
    
            def values(cls, value=None):
                if value is None:
                    return {choice[0]: unicode(choice[1]) for choice in cls.choices()}
                elif isinstance(value, list):
                    return {choice[0]: unicode(choice[1]) for choice in cls.choices() if choice[0] in value}
                else:
                    return unicode(dict(cls.choices()).get(value))
    
            def keys(cls, nil=False):
                items = [item[0] for item in cls.choices()]
                if nil:
                    items.append('')
    
                return items
    
            def combined_length(cls):
                return len(",".join(cls.values().keys()))
    
            def max_length(cls):
                return max(map(len, cls.values().keys()))
    
            cls_attrs['choices'] = classmethod(choices)
            cls_attrs['values'] = classmethod(values)
            cls_attrs['keys'] = classmethod(keys)
            cls_attrs['combined_length'] = classmethod(combined_length)
            cls_attrs['max_length'] = classmethod(max_length)
    
            return type(name, bases, cls_attrs)
    

    An example of an Enum is as follow:

    class SideHemType:
        __ordering__ = ['double', 'single']
        __metaclass__ = EnumMeta
    
        Single = "Single side hem for opaque fabrics"
        Double = "Double side hem for transparent fabrics"
    
    
      class TestEnumOrdering:
            print SideHemType.keys()
            print SideHemType.values() 
    

    By printing the Enum SideHemType first Double is printed and then Single. But I would like first Single and then Double.

  • Robert
    Robert about 10 years
    It has no effect to add ordering the Enum, see my example.
  • jbch
    jbch over 5 years
    @Robert __ordering__ is not magic, Antti also modified the code for EnumMeta, if you just add __ordering__ without using his EnumMeta code it will do nothing.
  • Michael
    Michael almost 4 years
    Worth noting: even with Python 3.4+, if you have your own list of Enum members that you want to sort, you have to specify a key (sorted([SideHemType.Double, SideHemType.Single], key=lambda sht: sht.value)). The Enum members do not implement __lt__. You get a nice little TypeError: '<' not supported between instances of your Enum type.
  • mac
    mac over 3 years
    @Michael-Where'sClayShirky....but then the sort order is based on the value, not the order declared, right?
  • ChrisCantrell
    ChrisCantrell about 3 years
    If you are using the Python 3.4+ Enum, then you can define your own __lt__ operator inside the Enum definition to compare name, value, or anything else you want.
  • Thorben Croisé
    Thorben Croisé almost 3 years
    Simple and elegant, be aware that this became available in Python 3.4.