'Enum in Python doesn't work as expected

I found a very strange behavior in the Enum class in Python. So the enumerated type is simple:

from enum import Enum
Analysis = Enum('Analysis', 'static dynamic')

So I use this enumerated type in for step objects so that they store it in the attribute analysis, as follows:

class Step:
    def __init__(self):
        self.analysis = None
        self.bcs = []

Very simple so far, so when I have a few of these steps in a list, then I try to see the enumerated type and it has been assigned correctly. But they are not equal:

# loop over steps
for s, step in enumerate(kwargs['steps']):
    print(kwargs)
    print(step)
    print(step.analysis)
    print("test for equality: ",(step.analysis == Analysis.static))
    quit()

which prints

{'mesh': <fem.mesh.mesh.Mesh object at 0x10614d438>,
 'steps': [<hybrida.fem.step.Step object at 0x10614d278>,
           <hybrida.fem.step.Step object at 0x10616a710>,
           <hybrida.fem.step.Step object at 0x10616a390>]}
Step:
  analysis: Analysis.static
  bcs: [<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a0f0>,
        <hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a320>,
        <hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a3c8>,
        <hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a470>,
        <hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a518>,
        <hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a5c0>,
        <hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a668>]
Analysis.static
test for equality:  False

This is not correct, but I have no ideas on how to debug this.

UPDATE

Following the suggestion by @martineau, I created an IntEnum instead and that solved my problem. Yet, I don't understand why the normal Enum doesn't work.



Solution 1:[1]

In the comments, you say:

The input file contains many steps, and every time I add a new step I have to set up the analysis type

If I understand you correctly, you're saying that you create a new Enum object each time you add a new step. This may be why you're seeing your "bug". The values of two different Enum objects, despite having the same name and order, do not necessarily compare as equal. For example:

import enum
Analysis1 = enum.Enum("Analysis", "static dynamic")
Analysis2 = enum.Enum("Analysis", "static dynamic")

But:

>>> Analysis1.static == Analysis2.static
False

This happens because the equality operator is not defined for Enum objects, as far as I can tell, so the default behavior of checking ids is used.

As @martineau suggests in the comments, one way of avoiding this issue is to instead use the IntEnum type, which subclasses int, and therefore defines the equality operator in terms of the value of the Enum, not the id:

import enum
Analysis1 = enum.IntEnum("Analysis", "static dynamic")
Analysis2 = enum.IntEnum("Analysis", "static dynamic")

Then:

>>> Analysis1.static == Analysis2.static
True

Why have Enum and IntEnum?

It may seem at first glance that IntEnum is always what we want. So what's the point of Enum?

Suppose you want to enumerate two sets of items, say, fruits and colors. Now, "orange" is both a fruit, and a color. So we write:

Fruits = enum.IntEnum("Fruits", "orange apple lemon")
Colors = enum.IntEnum("Colors", "orange red blue")

But now:

>>> Fruits.orange == Colors.orange
True

But, philosophically speaking, "orange" (the fruit) is not the same as "orange" (the color)! Shouldn't we be able to distinguish the two? Here, the subclassing of int by IntEnum works against us, as both Fruits.orange and Colors.orange equate to 1. Of course, as we saw above, comparison of Enums compares ids, not values. Since Fruits.orange and Colors.orange are unique objects, they do not compare as equal:

Fruits = enum.Enum("Fruits", "orange apple lemon")
Colors = enum.Enum("Colors", "orange red blue")

So that:

>>> Fruits.orange == Colors.orange
False

and we no longer live in a world where some colors are things that you can find in the produce section of your local grocery store.

Solution 2:[2]

In case anyone else finds themselves here after us, we experienced the same issue. We were able to trace it back to an unintentional mixing of absolute and relative imports, in a manner similar to the situation described below.

# File: package/module/analysis_types.py
Analysis = enum.Enum("Analysis", "static dynamic")
# File: package/module/static_thing.py
from .analysis_types import Analysis

class StaticThing:
    ...
    analysis = Analysis.static
    ...
# File: package/module/static_thing_test.py
from package.module.static_thing import StaticThing
from .analysis_types import Analysis

# This throws an AssertionError because as
#  id(StaticThing.analysis) != id(Analysis.static)
assert StaticThing.analysis == Analysis.static

Expected behavior was restored with the following changes:

# File: package/module/static_thing_test.py
from .static_thing import StaticThing
from .analysis_types import Analysis

# This does NOT throw an AssertionError because as
#  id(StaticThing.analysis) == id(Analysis.static)
assert StaticThing.analysis == Analysis.static

Solution 3:[3]

For anyone who's stuck in the situation as described by Austin Basye, and cannot resolve the issue by just changing the imports, try using the enum value.

That is, if obj1.type1 == obj2.type1 is False (but it should be True), check if obj1.type1.value == obj2.type2.value works.

In this case, Analysis1.static.value == Analysis2.static.value should return the correct value all the time.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Shrinidhi H R