Issue
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 thenFingers.__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__
andfrom_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?
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.
Answered By - Ethan Furman
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.