Using Enum and | as Dictionary Key

Enum as Dictionary keys

I believe that's because of covariance.

In short:

aDictionary will be a Dictionary<SomeEnum, SomeClass>, but in the current context it is known as Dictionary<Enum, SomeClass>.

Had your declaration been allowed, the compiler should afterwards let you do:

aDictionary.Add(someValueFromAnotherEnumUnrelatedToSomeEnum, aValue);

which is obviously inconsistent with respect to the actual type of the dictionary.

That's why co-variance is not allowed by default and you have to explicitly enable it in cases where it makes sense.

The conclusion is that you have to specify the type exactly:

Dictionary<SomeEnum, SomeClass> aDictionary = 
new Dictionary<SomeEnum, SomeClass>();

Using Enum and | as dictionary key

Unless otherwise specified, an enum maps to an integral value. Dictionary lookups will be based on the integral value of the enumerated value.

Since you didn't override the values assigned to your enum, they number from zero. This means AirForce == 0, Army == 1, and so on.

When you combine AirForce | Army | Marines, you're really doing 0 | 1 | 3, which is 3.

The way you've set up your dictionary, you've added entries for the integral values of 3 (with the bitwise operand), 2 (for Navy) and 4 (for Marines).

I'm afraid the simplest approach for what you want is to add an explicit entry for each branch of the military.

Python dictionary with enum as key

A simple solution would be to slightly modify your Color object and then subclass dict to add a test for the key. I would do something like this:

class Color(Enum):
RED = "RED"
GREEN = "GREEN"
BLUE = "BLUE"

@classmethod
def is_color(cls, color):
if isinstance(color, cls):
color=color.value
if not color in cls.__members__:
return False
else:
return True


class ColorDict(dict):

def __setitem__(self, k, v):
if Color.is_color(k):
super().__setitem__(Color(k), v)
else:
raise KeyError(f"Color {k} is not valid")

def __getitem__(self, k):
if isinstance(k, str):
k = Color(k.upper())
return super().__getitem__(k)

d = ColorDict()

d[Color.RED] = 123
d["RED"] = 456
d[Color.RED]
d["foo"] = 789

In the Color class, I have added a test function to return True or False if a color is/isn't in the allowed list. The upper() function puts the string in upper case so it can be compared to the pre-defined values.

Then I have subclassed the dict object to override the __setitem__ special method to include a test of the value passed, and an override of __getitem__ to convert any key passed as str into the correct Enum. Depending on the specifics of how you want to use the ColorDict class, you may need to override more functions. There's a good explanation of that here: How to properly subclass dict and override __getitem__ & __setitem__

Using an enum as a dictionary key

You can do it as follows:

type EnumDictionary<T extends string | symbol | number, U> = {
[K in T]: U;
};

enum Direction {
Up,
Down,
}

const a: EnumDictionary<Direction, number> = {
[Direction.Up]: 1,
[Direction.Down]: -1
};

I found it surprising until I realised that enums can be thought of as a specialised union type.

The other change is that enum types themselves effectively become a
union of each enum member. While we haven’t discussed union types yet,
all that you need to know is that with union enums, the type system is
able to leverage the fact that it knows the exact set of values that
exist in the enum itself.

The EnumDictionary defined this way is basically the built in Record type:

type Record<K extends string, T> = {
[P in K]: T;
}

Why am I getting a KeyError when trying to use an Enum as a dictionary key in another file?

This is exactly why you should never import the same module that you're running as a script. You end up with two completely independent module objects, enum_test and __main__, with their own completely independent global namespaces, containing separate objects that were constructed from the same code.

__main__.main() builds a dict, and fills it with __main__.MyEnum keys.

print_test_file.print_test takes that dict as a parameter. But then it tries to search it using enum_test.MyEnum.A. And that key does not exist in that dict. So you get a KeyError.


Your modules also have circular dependencies, which is obscured by this problem. I'm pretty sure you'd actually get away with the circular dependencies in this case if nothing else was wrong, but it's still confusing to have to think through the order of how top-level module code gets executed, so it's better to avoid them.


The easy way to fix both problems at once is to move the shared code into a separate shared module, which both your script and your test module can import.

# enum_test.py
from my_enum import MyEnum
import print_test_file

def main():
enumDict = dict()
enumDict[MyEnum.A] = 'abcd'
enumDict[MyEnum.B] = 'efgh'

print(enumDict[MyEnum.A])
print_test_file.print_test(enumDict)

if __name__ == "__main__":
main()

# print_test_file.py
import my_enum

def print_test(enumDict):
print(enumDict[my_enum.MyEnum.A])

# my_enum.py
from enum import Enum

class MyEnum(Enum):
A = 1
B = 2

Now, there's no module that imports the script, and no module that imports the module that imports it. (In technical terms, instead of a graph with cycles in it, you just have a tree, with the script at the root.)


Every few years, someone suggests changing Python to eliminate this problem, but there really is no good answer. They could make it an error to import the same module being run as a script, but that could break some uncommon but important use cases (like multiprocessing). Or they could make it “just work” by doing an implicit sys.modules['enum_test'] = sys.modules['__main__'] somewhere—but there really is no “somewhere” that wouldn’t turn the circular dependency in your code (and almost every other example of this problem) from mostly harmless to a serious bug.

(By the way, I vaguely remember that in one of the discussions, one of the core devs who’s also a teacher mentioned that very few of his students ever run into this problem, but the ones who do tend to end up near the top of the class, so at least you have that to feel good about.)

Enum object as dictionary key

You could use computed property names with brackets.

var State = { DEFAULT: 0, ACTIVE: 1, INACTIVE: 2, ALERT: 3 },    statesDict = {        [State.ACTIVE]: { color: 0x00ff00 },        [State.INACTIVE]: { color: 0x000000 }    };
console.log(statesDict);

Generate typed dictionary with a for loop using an enum as key type but without using `?` to mark undefined in TypeScript

You're trying to define runtime behavior (i.e. the creation of an object) based on a construct that only exists at compile time (i.e. an enum).

At compile time, your [key in Students] type expression evaluates to [0, 1, 2], and not ["A", "B", "C"] as you (seem to) assume.

At runtime however, this is what your TypeScript enum currently transpiles to in JavaScript:

var Students;
(function (Students) {
Students[Students["A"] = 0] = "A";
Students[Students["B"] = 1] = "B";
Students[Students["C"] = 2] = "C";
})(Students || (Students = {}));

If you would console.log(Students), you'll get the following output:

{
"0": "A",
"1": "B",
"2": "C",
"A": 0,
"B": 1,
"C": 2
}

As you can see, it's an object, not an array, so your for (let name of Students) {} will not work (actually, it won't even compile). On the other hand, for (let name in Students) {} will work, but will yield the 6 keys listed above instead of your 3 enum constants.

That's the runtime value that you would have to base your object creation on, so to get the behavior that you want, you'd need to filter out the non-numeric keys and create an object using the remaining keys. Obviously, the TypeScript compiler will not be able to infer the type of that object, so you would still have to specify the type that you've created yourself.

const studentInfo: { [key in Students]: Info } =
Object.values(Students)
.filter((v) => typeof v === 'number')
.reduce((a, v) => ({...a, [v]: new Info()}), {})
as { [key in Students]: Info };

Then to access the value in the studentInfo object, the correct syntax would be:

studentInfo[Students.A].name;

Obviously, due to the fact that you're using enums far beyond their intended purpose, all of this feels very hacky.


To summarize:

  • Don't base runtime behavior on compile-time constructs
  • You probably shouldn't be using an enum here


Related Topics



Leave a reply



Submit