Reputation: 7500
I'm learning how to use Enum
classes in Python, and have found that whenever I need to access the actual value of the enum, I need to append the .value
property:
from enum import Enum
class Pets(Enum):
DOG = "Fido"
CAT = "Kitty"
Pets.DOG # yields Pets.DOG
Pets.DOG.value # yields Fido
As an exercise, I'm trying configure my Enum
class so that I do not need to continually access that value
property. My desired behavior is that when I call Pets.DOG
, I get Fido
as my value.
I'm tried to implement this with __getattr_(cls, item)
:
class Pets(Enum):
def __getattr__(self, item):
print(f"__getattr__ called with {item}")
return getattr(self, item).value
DOG = "Fido"
CAT = "Kitty"
if __name__ == "__main__":
pets = Pets()
pets.DOG
However, I receive a RecursionError: maximum recursion depth exceeded while calling a Python object
, and item
is a string value of _value_
. I'm not quite understanding why this behavior is happening - is this built in Python behavior, or because I am using a special class Enum
?
I did take a look at a similar SO post, but the solutions there were to use another module (inspect
), or access the __dict__
or dir()
and parse it yourself with a combination of conditionals or regex. Is there a better way to access the underlying Enum
's value?
Upvotes: 9
Views: 10427
Reputation: 619
You can inherit from str
, and the enum values will behave as strings in the same way that bool
s behave as int
s (essentially every way except type()
and repr()
).
class Pets(str, Enum):
DOG = "Fido"
CAT = "Kitty"
>>> Pets.DOG
<Pets.DOG: 'Fido'>
>>> Pets.DOG == 'Fido'
True
>>> {'Fido': 5}[Pets.DOG]
5
>>> {Pets.DOG: 5}['Fido']
5
Upvotes: 1
Reputation: 71
You said, "My desired behavior is that when I call Pets.DOG
, I get Fido as my value." Calling Pets.DOG
really calls print(repr(Pets.DOG))
, so my proposal is this:
class Pets(enum.Enum):
DOG = "Fido"
CAT = "Kitty"
def __repr__(self):
return self.value
One advantage to this approach is that you can still access the other features of Enum, such as Pets.DOG.name
That said, I would prefer to override __str__
rather than __repr__
because that achieves your goal of avoid typing .value
, but leaves intact the ability to see all information about the Enum
member when using repr()
.
I bumped into your question when trying to use namedtuples for my values instead of simple strings. In that case, I think __getattr__
is helpful, and just in case others find this post for a similar use, I am including what worked for me:
import enum
import collections
_ = lambda x : x
class XlateEnum(enum.Enum):
"""Enum whose members inherit the attributes of their values,
and which applies the assumed function _() for translations.
"""
def __getattr__(self, name):
if name == "_value_":
return enum.Enum.__getattribute__(self, name)
elif hasattr(self, "_value_") and hasattr(self._value_, name):
return _(getattr(self._value_, name))
return enum.Enum.__getattribute__(self, name)
def __setattr__(self, name, new_value):
if name == "_value_":
enum.Enum.__setattr__(self, name, new_value)
elif hasattr(self, "_value_") and hasattr(self._value_, name):
raise AttributeError("can't set attribute")
else:
enum.Enum.__setattr__(self, name, new_value)
def __delattr__(self, name):
if hasattr(self, "_value_") and hasattr(self._value_, name):
raise AttributeError("can't delete attribute")
else:
enum.Enum.__delattr__(self, name)
def __str__(self):
return self.str if hasattr(self, "str") else _(enum.Enum.__str__(self.value))
class Pet(XlateEnum):
"""My pets.
Attributes:
str: A localized str to name the Pet. How the Pet prints.
my: A string representing what I call instances of this Pet.
legs: The int number of legs of normal instances of this Pet.
"""
DOG = collections.namedtuple("PetValue", "str my legs")(_("Dog"), "Fido", 4)
CAT = collections.namedtuple("PetValue", "str my legs")(_("Cat"), "Kitty", 4)
print(Pet.DOG) # yields "Dog" (or translated string)
print(Pet.DOG.my) # yields "Fido" (which is not designated for translation)
Of course, you can remove the _()
feature. I find Enum
and namedtuple
to very useful for constants because it keeps them in proper relationship to each other, and I like to build all of my translation functionality into the constants themselves, so code like this just works:
import ipywidgets
ipywidgets.Dropdown(options=Pet)
Upvotes: 2
Reputation: 1124948
Don't use the enum class if you want to map attributes to strings. The whole point of the enum
module is to produce a set of singleton objects that represent an enumeration, not strings. From the module documentation:
An enumeration is a set of symbolic names (members) bound to unique, constant values. Within an enumeration, the members can be compared by identity, and the enumeration itself can be iterated over.)
Bold emphasis mine. Strings are not unique, constant values (I can create more "Fido"
strings at will) and are not designed to be compared by identity (even though sometimes, for a subset of strings, you can).
Just define your own class with attributes that are strings, directly:
class Pets:
DOG = "Fido"
CAT = "Kitty"
Your infinite recursion error is caused by a misunderstanding on your part as to what that method is used for. Like all special methods, object.attr
looks up __getattr__
on the object type, meaning here that your method applies to instances of your Enum
subclass, the DOG
and CAT
attributes here, not to the class itself, and interferes with the EnumMeta
metaclass trying to test for the _value_
attibute, which is handled by your __getattr__
method with self
being the newly-minted Pets.DOG
instance, and item
set to '_value_'
, which then calls getattr(Pets.DOG, '_value_')
, which calls __getattr__
, etc.
For your approach to work, you'd have to subclass EnumMeta
and implement __getattribute__
on that subclass (__getattr__
is only ever called for missing attributes). However, take into account that __getattribute__
is used for all attribute access, so you have to take care to check for instances of the current class first:
class EnumDirectValueMeta(EnumMeta):
def __getattribute__(cls, name):
value = super().__getattribute__(name)
if isinstance(value, cls):
value = value.value
return value
class Pets(Enum, metaclass=EnumDirectValueMeta):
DOG = "Fido"
CAT = "Kitty"
at which point Pets.DOG
produces 'Fido'
.
Upvotes: 11