Ordering enum value in python
Asked Answered
L

6

11

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.

Latex answered 23/8, 2013 at 9:18 Comment(5)
I don't see any printing in the code you provided. My wild guess is that you are printing a dict, which does not provide any guarantee on the order of the keys.Liberal
I think the indenting is right at last...Dispassion
The dict is the class.__dict__, no there is no ordering for thatDispassion
Ordering is added in the code, but it doesnt solve my problem. By printing the keys its printing first single and then double. But as in the example it should be first double and then single.Latex
What? Your comment -- "But as in the example it should be first double then single" is the opposite of your post -- "but I would like first Single then Double". Which should it be?Alkanet
M
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()
Malda answered 23/8, 2013 at 10:14 Comment(2)
It has no effect to add ordering the Enum, see my example.Latex
@Latex __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.Noellanoelle
J
12

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.

Jinja answered 4/1, 2021 at 23:35 Comment(2)
Simple and elegant, be aware that this became available in Python 3.4.Shantelleshantha
If you only care about the relative positioning of the elements, you might want to use the auto() function of the enum package. This way it is quite easy to also add values in between the existing ones, if necessary.Full
A
9

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"
Alkanet answered 23/8, 2013 at 14:29 Comment(5)
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.Luzon
@Michael-Where'sClayShirky....but then the sort order is based on the value, not the order declared, right?Elizebethelizondo
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.Wadmal
Does IntEnum also remember the order of the enum members? More specifically, when you iterate over a IntEnum derived class, will you loop over the enum members in the same order as the order in which they are defined?Criseyde
@HelloGoodbye: Yes.Alkanet
F
6

How about this

class MyEnum(enum.Enum):
    first_item = 'bla bla'
    whatever = 'blubb'
    another_one = 'blobb'

    def __lt__(self, other: 'MyEnum'):
        if self == other:
            return False
        # the following works because the order of elements in the definition is preserved
        for elem in MyEnum:
            if self == elem:
                return True
            elif other == elem:
                return False
        raise RuntimeError('Bug: we should never arrive here')  # I just like being pedantic

    def __gt__(self, other):
        return not (self < other)

    def __ge__(self, other):
        if self == other:
            return True
        return not (self < other)
Fordo answered 12/4, 2022 at 8:39 Comment(4)
This feels like the right solution to me? __lt__ defines order, and defining it lets you use a simple enum.Enum, check orders, sort lists of your enum, etc.Hach
I created a neat solution using member_index. U just need to derive from OrderedStrEnum: GistPreparation
Beautiful. Thanks so much. Small note: you can use from __future__ import annotations and then you can declare the type without resort to a string in the __lt__ signatureLatrice
Cautions: you seem to need to implement __gt__ and __ge__ by hand as well for this to work totally as expected. total_ordering will not work.Latrice
M
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()
Malda answered 23/8, 2013 at 10:14 Comment(2)
It has no effect to add ordering the Enum, see my example.Latex
@Latex __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.Noellanoelle
A
0

To support a 'StrEnum':

class ExecutionState(enum.StrEnum):
    def _get_ordering(self):
        return {
            name: idx
            for idx, name in enumerate(self.__class__)
        }

    def __lt__(self, other):
        ordering = self._get_ordering()
        return ordering[self] < ordering[other]

    def __gt__(self, other):
        ordering = self._get_ordering()
        return ordering[self] > ordering[other]
Ammamaria answered 20/1, 2024 at 1:28 Comment(0)
A
0

The implementation that I use is the below which allows for sorting and for set comparison for string enums as well which are imperative for LLM usage. It maintains the order you specify and not the sorted string order.

from enum import Enum


class SortableEnum(Enum):
    def __lt__(self, other):
        if other is None:
            return False
        if not isinstance(other, self.__class__):
            return NotImplemented
        order = list(self.__class__)
        return order.index(self) < order.index(other)

    def __le__(self, other):
        if other is None:
            return False
        return self < other or self == other

    def __gt__(self, other):
        if other is None:
            return True
        return not self <= other

    def __ge__(self, other):
        if other is None:
            return True
        return not self < other
    

Usage

class StringEnum(str, SortableEnum):
    VALUE_ONE = "value_one"
    VALUE_TWO = "avalue_two"
Atropine answered 30/7, 2024 at 8:42 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.