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
How to Fix Json.Net (Newtonsoft.Json) Runtime File Load Exception
How to Use Variable in Feature File
How to Get the Currently Loggedin Windows Account from an ASP.NET Page
Redirect from Controller to Another View of Another Controller
Programmatically Close Aspx Page from Code Behind
How to Refresh or Show Immediately in Datagridview After Inserting
How to Get the List of Properties of a Class
How to Add a Run Button and Compile Button on the Toolbar in Visual Studio
How to Delete All Files in an Azure File Storage Folder
How to Turn Off Brackets/Quotes Auto-Completion in Visual Studio
Sending Array of Bytes from Client to Server
Entity Framework Migrations Renaming Tables and Columns
How to Generate a System (Pc/Laptop) Hardware Unique Id in C#
How to Read Appsettings.Json With Array of Values
How to Format a String as a Telephone Number in C#