11

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.

aaragon
  • 2,314
  • 4
  • 26
  • 60
  • I read a file. I tried to come up with a minimalistic example, but it works fine so I have no idea on what's wrong. – aaragon Jan 24 '15 at 11:51
  • If I do it the way you suggested I get `AttributeError: static`. See [this post](http://stackoverflow.com/questions/36932/how-can-i-represent-an-enum-in-python). – aaragon Jan 24 '15 at 11:53
  • Oh, I see, I tried with the different `enum` package. Maybe you should add this to your question. – tobias_k Jan 24 '15 at 11:56
  • What different enum package? The package I'm using is stated in the post. – aaragon Jan 24 '15 at 11:58
  • So how *do* you set `step.analysis`? Add the relevant code to your question please. – Lukas Graf Jan 24 '15 at 12:07
  • @LukasGraf while reading a file, I set it like this: `self.steps[-1].analysis = Analysis.static`. – aaragon Jan 24 '15 at 12:08
  • So you're always setting the attribute on the *last* step in the list of `self.steps` - is that what you want? – Lukas Graf Jan 24 '15 at 12:10
  • The input file contains many steps, and every time I add a new step I have to set up the analysis type. So this works file. You see that later when I check the analysis attribute of the step it prints `Analysis.static`, but this compares to false! It's really strange. – aaragon Jan 24 '15 at 12:13

3 Answers3

20

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.

jme
  • 19,895
  • 6
  • 41
  • 39
  • Thanks for your answer. I did a `grep` search, and there's only one point in the code where I create the enum: `fem/definitions.py:Analysis = Enum('Analysis', 'static dynamic')`. So I import the Analysis from the `definitions.py` file. – aaragon Jan 24 '15 at 13:33
  • @aaragon I'd heavily bet that that is still what is happening. If you check the `id` of `step.analysis` and `Analysis.static`, I'm guessing you'll see that they're different. That is, they're two different objects. Are you comparing `Enum`s that you've unpickled? – jme Jan 24 '15 at 13:57
  • You're right! I'm not using unpickled Enums. I checked the ids just after assignment: `print(id(self.steps[-1].analysis)); print(id(Analysis.static))` and it gives me the same number: 4329054048. Later on I do the same: `print(id(step.analysis)); print(id(Analysis.static))` and the second id is different: 4393709864. Why is this happening? The entire point of comparing enums is to be able to do which comparison on integer values (coming from a C++ world). – aaragon Jan 24 '15 at 14:10
  • @aaragon: You may be able to avoid the problem by using the [`IntEnum`](https://docs.python.org/3/library/enum.html#intenum) class which is derived from `int`. – martineau Jan 24 '15 at 14:28
  • Well that solved my problem, but it doesn't fix the issue. Is that a bug in the `Enum` python class? – aaragon Jan 24 '15 at 14:58
  • @aaragon Nope, it's not a bug in `Enum`. Somewhere in your code you're creating another instance of `Analysis`, different from the one you've used previously. It's impossible to know how you've done this without seeing more of your code, but it may be worthwhile for you to look through it and see what's going on, because your code is doing something you didn't expect, and that's generally not a good thing ;). – jme Jan 24 '15 at 15:02
  • I double-checked checked that. I understand what you say, but it's not the case as the value of `step.analysis` is the same since creation. What is different is the value I obtain for `Analysis.static` when I do the comparison. It is as if the id of calling `Analysis.static` changes depending on where you call it from, even if it's defined on a single file (as I'm defining it on one file and importing it into the other modules). – aaragon Jan 24 '15 at 15:59
  • @aaragon The `id` of `step.analysis` remaining the same is to be expected, even if `Analysis` is reassigned to a different `Enum`. You have (at least) two different "Analysis" objects -- there's no way around that. The question is: how are two being made? This is something that probably requires looking at all of your code, and certainly isn't answerable with what you have posted above. Is your project shared on GitHub? – jme Jan 24 '15 at 16:12
  • It's not yet on the GitHub for now. In my `definitions.py` file I have only `Analysis = Enum('Analysis', 'static dynamic')`. Then I just call `Analysis.static` throughout the code to assign it (in the case of the step's attribute `analysis`) and then to compare it when I do `step.analysis == Analysis.static`. Is it possible that calling `Analysis.static` creates another object? – aaragon Jan 24 '15 at 16:35
  • @aaragon Are you doing any sort of module reloading or messing with import hooks? If you're importing `definitions.Analysis` in multiple modules, the first import should cause `definitions` to run and `Analysis` to be defined. It'll then be cached in `sys.modules`, and other modules which import `Analysis` should get the same `Analysis` object -- unless something strange is occurring where `definitions` is re-run upon importing. You can check this by looking at `id(definitions)` when you see `definitions.Analysis` change `id`s. – jme Jan 24 '15 at 16:59
  • I checked and the `definitions` id is the same. So this rules out the possibility of reloading the module? I'm really lost. – aaragon Jan 24 '15 at 19:14
  • @aaragon Ultimately, I'm afraid there's little I or any one can do to debug your code without seeing it or being able to put it through a debugger. It would be great if you could give us a small snippet of code that reproduces the error, though I understand that this isn't always possible (though it *does* suggest that your project could be made more modular). Otherwise, if you put it up on github I might be able to take a look when I'm not super busy. – jme Jan 24 '15 at 20:07
11

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
Austin Basye
  • 123
  • 1
  • 6
  • 1
    Exactly came here. Why tests are false on python enum? It is the same enum!... aha... - so confusing! – MajesticRa Mar 30 '21 at 02:23
  • 3
    Definitely an import loading/reloading issue. I ran into this and my particular issue wasn't in a test, but a notebook with that used the `%autoreload` magic. Solution was to stop using that magic. – Bryan Dannowitz May 04 '22 at 13:25
0

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.