pythonenumskeyerror

Why are enums incompatible across python packages?


An enum is declared in an imported package and identically in the importer. Same value, but Python treats the imported enum value as different for some reason. Package 1 is a parser that I wrote which outputs a dictionary containing some values from this enum declared in the parser package:

class NodeFace(Enum):
    TOP = 0
    BOTTOM = 1
    RIGHT = 2
    LEFT = 3

So, it parses along and then maps some text to these values in a dictionary that will be accessible to the importer. In case it matters, the parser is built using Python 3.13.

Now the importer, built using Python 3.12, also declares the same enum in its own local files. Identical to above. Additionally, I use this dictionary to find opposite sides, declared in the importer:

OppositeFace = {
    NodeFace.TOP: NodeFace.BOTTOM,
    NodeFace.BOTTOM: NodeFace.TOP,
    NodeFace.LEFT: NodeFace.RIGHT,
    NodeFace.RIGHT: NodeFace.LEFT
}

In the importer I assign enum values from the parser output dict to variables tface and pface. And, I test to make sure they look right by printing to console and sure enough I get values like: <NodeFace.BOTTOM: 1>. In fact, I'm looking at them in the debugger, no problem so far. Now the fun starts...

if OppositeFace[tface] == pface:

This fails on a KeyError on the OppositeFace lookup. BUT, if I do the lookup in the debugger console with: OppositeFace[NodeFace.TOP], it works just fine.

I did all kinds of tests and all I could figure is that the problem was in defining the enum twice, once in the parser and again in the importer and, despite all the values matching in the debugger and console, some internal value is different and causing the dict lookup to fail.

My solution was to eliminate the Enum from my parser and just pass strings 'TOP', 'BOTTOM', etc. Then, on the importer side I do OppositeFace[NodeFace[tface]] where tface is now the string and it works fine.

Can anyone tell me why exactly this happens? Just curious at this point.


Solution

  • This is just how Python normally works. Defining two classes the same way doesn't make them the same class, or make their instances equal. Even if they print the same, Python doesn't compare objects based on how they print.

    You have two separate enums with separate members. Unless you implement different behavior (which you didn't), enums inherit the default identity-based __eq__ and __hash__ implementations from object. That means an enum member is only equal to itself.

    The only relevant enum magic is that writing TOP = 0 in an enum definition makes TOP an instance of the enum class, rather than the int 0. Integers compare by numeric value rather than identity (and also there's a CPython implementation detail that would make the ints the same object anyway), so if your enum members really were just ints, they would compare equal. They're not, though.