Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare Enums in Python?

Since Python 3.4, the Enum class exists.

I am writing a program, where some constants have a specific order and I wonder which way is the most pythonic to compare them:

class Information(Enum):
    ValueOnly = 0
    FirstDerivative = 1
    SecondDerivative = 2

Now there is a method, which needs to compare a given information of Information with the different enums:

information = Information.FirstDerivative
print(value)
if information >= Information.FirstDerivative:
    print(jacobian)
if information >= Information.SecondDerivative:
    print(hessian)

The direct comparison does not work with Enums, so there are three approaches and I wonder which one is preferred:

Approach 1: Use values:

if information.value >= Information.FirstDerivative.value:
     ...

Approach 2: Use IntEnum:

class Information(IntEnum):
    ...

Approach 3: Not using Enums at all:

class Information:
    ValueOnly = 0
    FirstDerivative = 1
    SecondDerivative = 2

Each approach works, Approach 1 is a bit more verbose, while Approach 2 uses the not recommended IntEnum-class, while and Approach 3 seems to be the way one did this before Enum was added.

I tend to use Approach 1, but I am not sure.

Thanks for any advise!

like image 514
Sebastian Werk Avatar asked Sep 01 '16 09:09

Sebastian Werk


People also ask

Can we compare two enums?

There are two ways for making comparison of enum members :equals method uses == operator internally to check if two enum are equal. This means, You can compare Enum using both == and equals method.

Is enum hashable Python?

Use Enum Members in DictionariesPython enumerations are hashable. This means you can use the enumeration members in dictionaries.

Can we compare enum with INT?

Cast Int To Enum may be of some help. Go with the 2nd option. The 1st one can cause an exception if the integer is out of the defined range in your Enumeration. In current example I compare to 'magic number' but in real application I am getting data from integer field from DB.


3 Answers

You should always implement the rich comparison operaters if you want to use them with an Enum. Using the functools.total_ordering class decorator, you only need to implement an __eq__ method along with a single ordering, e.g. __lt__. Since enum.Enum already implements __eq__ this becomes even easier:

>>> import enum >>> from functools import total_ordering >>> @total_ordering ... class Grade(enum.Enum): ...   A = 5 ...   B = 4 ...   C = 3 ...   D = 2 ...   F = 1 ...   def __lt__(self, other): ...     if self.__class__ is other.__class__: ...       return self.value < other.value ...     return NotImplemented ...  >>> Grade.A >= Grade.B True >>> Grade.A >= 3 Traceback (most recent call last):   File "<stdin>", line 1, in <module> TypeError: unorderable types: Grade() >= int() 

Terrible, horrible, ghastly things can happen with IntEnum. It was mostly included for backwards-compatibility sake, enums used to be implemented by subclassing int. From the docs:

For the vast majority of code, Enum is strongly recommended, since IntEnum breaks some semantic promises of an enumeration (by being comparable to integers, and thus by transitivity to other unrelated enumerations). It should be used only in special cases where there’s no other choice; for example, when integer constants are replaced with enumerations and backwards compatibility is required with code that still expects integers.

Here's an example of why you don't want to do this:

>>> class GradeNum(enum.IntEnum): ...   A = 5 ...   B = 4 ...   C = 3 ...   D = 2 ...   F = 1 ...  >>> class Suit(enum.IntEnum): ...   spade = 4 ...   heart = 3 ...   diamond = 2 ...   club = 1 ...  >>> GradeNum.A >= GradeNum.B True >>> GradeNum.A >= 3 True >>> GradeNum.B == Suit.spade True >>>  
like image 197
juanpa.arrivillaga Avatar answered Oct 03 '22 12:10

juanpa.arrivillaga


I hadn'r encountered Enum before so I scanned the doc (https://docs.python.org/3/library/enum.html) ... and found OrderedEnum (section 8.13.13.2) Isn't this what you want? From the doc:

>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True
like image 30
nigel222 Avatar answered Oct 03 '22 13:10

nigel222


Combining some of the above ideas, you can subclass enum.Enum to make it comparable to string/numbers and then build your enums on this class instead:

import numbers
import enum


class EnumComparable(enum.Enum):
    def __gt__(self, other):
        try:
            return self.value > other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value > other
        except:
            pass
        return NotImplemented

    def __lt__(self, other):
        try:
            return self.value < other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value < other
        except:
            pass
        return NotImplemented

    def __ge__(self, other):
        try:
            return self.value >= other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value >= other
            if isinstance(other, str):
                return self.name == other
        except:
            pass
        return NotImplemented

    def __le__(self, other):
        try:
            return self.value <= other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value <= other
            if isinstance(other, str):
                return self.name == other
        except:
            pass
        return NotImplemented

    def __eq__(self, other):
        if self.__class__ is other.__class__:
            return self == other
        try:
            return self.value == other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value == other
            if isinstance(other, str):
                return self.name == other
        except:
            pass
        return NotImplemented
like image 34
VoteCoffee Avatar answered Oct 03 '22 12:10

VoteCoffee