Associating string representations with an Enum that uses integer values?
enums, python, python-3.x
Solution
This can be done with the stdlib `Enum`, but is much easier with `aenum`1:
from aenum import Enum
class Fingers(Enum):
_init_ = 'value string'
THUMB = 1, 'two thumbs'
INDEX = 2, 'offset location'
MIDDLE = 3, 'average is not median'
RING = 4, 'round or finger'
PINKY = 5, 'wee wee wee'
def __str__(self):
return self.string
If you want to be able to do look-ups via the string value then implement the new class method `_missing_value_` (just `_missing_` in the stdlib):
from aenum import Enum
class Fingers(Enum):
_init_ = 'value string'
THUMB = 1, 'two thumbs'
INDEX = 2, 'offset location'
MIDDLE = 3, 'average is not median'
RING = 4, 'round or finger'
PINKY = 5, 'wee wee wee'
def __str__(self):
return self.string
@classmethod
def _missing_value_(cls, value):
for member in cls:
if member.string == value:
return member
1 Disclosure: I am the author of the Python stdlib `Enum`, the `enum34` backport, and the Advanced Enumeration (`aenum`) library.
Problem
I'm trying to create an enum that has integer values, but which can also return a display-friendly string for each value. I was thinking that I could just define a dict mapping values to strings and then implement `__str__` and a static constructor with a string argument, but there's a problem with that... (Under different circumstances I could have just made the underlying data type for this Enum a string rather than an integer, but this is being used as a mapping for an enum database table, so both the integer value and the string are meaningful, the former being a primary key.) ``` from enum import Enum class Fingers(Enum): THUMB = 1 INDEX = 2 MIDDLE = 3 RING = 4 PINKY = 5 _display_strings = { THUMB: "thumb", INDEX: "index", MIDDLE: "middle", RING: "ring", PINKY: "pinky" } def __str__(self): return self._display_strings[self.value] @classmethod def from_string(cls, str1): for val, str2 in cls._display_strings.items(): if str1 == str2: return cls(val) raise ValueError(cls.__name__ + ' has no value matching "' + str1 + '"') ``` When converting to string, I get the following error: ``` >>> str(Fingers.RING) Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> str(Fingers.RING) File "D:/src/Hacks/PythonEnums/fingers1.py", line 19, in __str__ return self._display_strings[self.value] TypeError: 'Fingers' object is not subscriptable ``` It seems that the issue is that an Enum will use all class variables as the enum values, which causes them to return objects of the Enum type, rather than their underlying type. A few workarounds I can think of include: - Referring to the dict as `Fingers._display_strings.value`. (However then `Fingers.__display_strings` becomes a valid enum value!) - Making the dict a module variable instead of a class variable. - Duplicating the dict (possibly also breaking it down into a series of `if` statements) in the `__str__` and `from_string` functions. - Rather than make the dict a class variable, define a static method `_get_display_strings` to return the dict, so it doesn't become an enum value. Note that the initial code above and workaround `1.` uses the underlying integer values as the dict keys. The other options all require that the dict (or `if` tests) are defined somewhere other than directly in the class itself, and so it must qualify these values with the class name. So you could only use, e.g., `Fingers.THUMB` to get an enum object, or `Fingers.THUMB.value` to get the underlying integer value, but not just `THUMB`. If using the underlying integer value as the dict key, then you must also use it to look up the dict, indexing it with, e.g., `[Fingers.THUMB.value]` rather than just `[Fingers.THUMB]`. So, the question is, what is the best or most Pythonic way to implement a string mapping for an Enum, while preserving an underlying integer value?