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:
Fingers._display_strings.value
. (However then Fingers.__display_strings
becomes a valid enum value!)if
statements) in the __str__
and from_string
functions._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?
We can use Enum. GetName static method to convert an enum value to a string. The following code uses Enum. GetName static method that takes two arguments, the enum type and the enum value.
No, we can have only strings as elements in an enumeration.
To get the value of enum we can simply typecast it to its type. In the first example, the default type is int so we have to typecast it to int. Also, we can get the string value of that enum by using the ToString() method as below.
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.
Maybe I am missing the point here, but if you define
class Fingers(Enum):
THUMB = 1
INDEX = 2
MIDDLE = 3
RING = 4
PINKY = 5
then in Python 3.6 you can do
print (Fingers.THUMB.name.lower())
which I think is what you want.
The python docs have a somewhat abstract example here
, from which I was able to come up with this solution
I have added an explanation inline, as comments.
# we could also do class Finger(IntEnum) it's equivalent.
class Finger(int, Enum):
def __new__(cls, value, label):
# Initialise an instance of the Finger enum class
obj = int.__new__(cls, value)
# Calling print(type(obj)) returns <enum 'Finger'>
# If we don't set the _value_ in the Enum class, an error will be raised.
obj._value_ = value
# Here we add an attribute to the finger class on the fly.
# One may want to use setattr to be more explicit; note the python docs don't do this
obj.label = label
return obj
THUMB = (1, 'thumb')
INDEX = (2, 'index')
MIDDLE = (3, 'middle')
RING = (4, 'ring')
PINKY = (5, 'pinky')
@classmethod
def from_str(cls, input_str):
for finger in cls:
if finger.label == input_str:
return finger
raise ValueError(f"{cls.__name__} has no value matching {input_str}")
So let's test it.
In [99]: Finger(1)
Out[99]: <Finger.THUMB: 1>
In [100]: Finger.from_str("thumb")
Out[100]: <Finger.THUMB: 1>
In [101]: Finger.THUMB
Out[101]: <Finger.THUMB: 1>
In [102]: Finger.THUMB.label
Out[102]: 'thumb'
The last test here is quite important, the __str__
method is automatically created depending on the inheritance class Finger(int, Enum)
.
If this was instead class Finger(str, Enum)
and obj = int.__new__(cls, value)
became obj = str.__new__(cls, value)
all the checks above would work but the call to __str__
would've raised an error.
In [103]: f"Finger.THUMB"
Out[103]: '1'
An example of it being used in python3 standard library http.HTTPStatus
Another solution I came up with is, since both the integers and the strings are meaningful, was to make the Enum values (int, str)
tuples, as follows.
from enum import Enum
class Fingers(Enum):
THUMB = (1, 'thumb')
INDEX = (2, 'index')
MIDDLE = (3, 'middle')
RING = (4, 'ring')
PINKY = (5, 'pinky')
def __str__(self):
return self.value[1]
@classmethod
def from_string(cls, s):
for finger in cls:
if finger.value[1] == s:
return finger
raise ValueError(cls.__name__ + ' has no value matching "' + s + '"')
However, this means that a Fingers
object's repr will display the tuple rather than just the int, and the complete tuple must be used to create Fingers
objects, not just the int. I.e. You can do f = Fingers((1, 'thumb'))
, but not f = Fingers(1)
.
>>> Fingers.THUMB
<Fingers.THUMB: (1, 'thumb')>
>>> Fingers((1,'thumb'))
<Fingers.THUMB: (1, 'thumb')>
>>> Fingers(1)
Traceback (most recent call last):
File "<pyshell#25>", line 1, in <module>
Fingers(1)
File "C:\Python\Python35\lib\enum.py", line 241, in __call__
return cls.__new__(cls, value)
File "C:\Python\Python35\lib\enum.py", line 476, in __new__
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 1 is not a valid Fingers
An even more complex workaround for that involves subclassing Enum
's metaclass to implement a custom __call__
. (At least overriding __repr__
is much simpler!)
from enum import Enum, EnumMeta
class IntStrTupleEnumMeta(EnumMeta):
def __call__(cls, value, names=None, *args, **kwargs):
if names is None and isinstance(value, int):
for e in cls:
if e.value[0] == value:
return e
return super().__call__(value, names, **kwargs)
class IntStrTupleEnum(Enum, metaclass=IntStrTupleEnumMeta):
pass
class Fingers(IntStrTupleEnum):
THUMB = (1, 'thumb')
INDEX = (2, 'index')
MIDDLE = (3, 'middle')
RING = (4, 'ring')
PINKY = (5, 'pinky')
def __str__(self):
return self.value[1]
@classmethod
def from_string(cls, s):
for finger in cls:
if finger.value[1] == s:
return finger
raise ValueError(cls.__name__ + ' has no value matching "' + s + '"')
def __repr__(self):
return '<%s.%s %s>' % (self.__class__.__name__, self.name, self.value[0])
One difference between this implementation and a plain int Enum is that values with the same integer value but a different string (e.g. INDEX = (2, 'index')
and POINTER = (2, 'pointer')
) would not evaluate as the same Finger
object, whereas with a plain int Enum, Finger.POINTER is Finger.INDEX
would evaluate to True
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With