如何直接将Enum的值作为类属性访问?

时间:2019-03-01 18:30:46

标签: python enums

我正在学习如何在Python中使用Enum类,并且发现,每当我需要访问枚举的实际值时,都需要附加.value属性:

from enum import Enum
class Pets(Enum):
    DOG = "Fido"
    CAT = "Kitty"

Pets.DOG # yields Pets.DOG
Pets.DOG.value # yields Fido

作为练习,我正在尝试配置Enum类,这样我就不必连续访问该value属性。 我的期望行为是,当我致电Pets.DOG时,我得到Fido作为我的价值。

我试图用__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

但是,我收到一个RecursionError: maximum recursion depth exceeded while calling a Python object,并且item是一个字符串值_value_。我不太了解为什么会发生这种行为-这是内置于Python的行为,还是因为我使用的是特殊的类Enum

我确实看过类似的SO帖子,但解决方案是使用另一个模块(inspect)或访问__dict__dir()并自己解析条件或正则表达式的组合。有没有更好的方法来访问基础Enum的值?

2 个答案:

答案 0 :(得分:3)

如果要将属性映射到字符串,请不要使用枚举类。 enum模块的整个 point 用于产生一组表示枚举而不是字符串的单例对象。从模块文档中:

  

枚举是绑定到唯一,恒定值的一组符号名称(成员)。在枚举中,成员可以通过身份进行比较,并且枚举本身可以进行迭代。)

强调粗体。字符串不是唯一的,恒定的值(我可以随意创建更多"Fido"字符串),并且不能按标识进行比较(即使{{3 }}。

只需直接使用字符串属性定义您自己的类:

class Pets:
    DOG = "Fido"
    CAT = "Kitty"

您的无限递归错误是由于您对该方法的用途有误而引起的。像sometimes, for a subset of strings, you can一样,object.attr对象类型上查找__getattr__,这意味着您的方法适用于{{的实例 1}}子类,这里的EnumDOG属性,而不是类本身,并且干扰了CAT元类,试图测试EnumMeta服装,这是由您的_value_方法处理,其中__getattr__是新创建的self实例,并且Pets.DOG设置为item,然后调用'_value_',会调用getattr(Pets.DOG, '_value_'),等等。

对于您的工作方法,您必须子集__getattr__并在该子类上实现all special methodsEnumMeta仅用于 missing 属性)。但是,请考虑到__getattr__用于 all 属性访问,因此您必须先检查当前类的实例:

__getattribute__

此时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" 产生Pets.DOG

答案 1 :(得分:1)

您说过,“我想要的行为是,当我调用 Pets.DOG 时,我将 Fido 作为我的值。”调用 Pets.DOG 确实调用了 print(repr(Pets.DOG)),所以我的建议是这样的:

class Pets(enum.Enum):
    DOG = "Fido"
    CAT = "Kitty"
    
    def __repr__(self):
        return self.value

这种方法的一个优点是您仍然可以访问 Enum 的其他功能,例如 Pets.DOG.name

也就是说,我更愿意覆盖 __str__ 而不是 __repr__,因为这样可以实现您避免键入 .value 的目标,但仍保留查看有关 {{ 的所有信息的能力使用 Enum 时的 1}} 成员。

我在尝试使用命名元组而不是简单字符串作为我的值时遇到了您的问题。在这种情况下,我认为 repr() 很有帮助,以防万一其他人发现这篇文章有类似用途,我将包含对我有用的内容:

__getattr__

当然,您可以删除 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) 功能。我发现 _()Enum 对常量非常有用,因为它使它们彼此保持适当的关系,而且我喜欢将我所有的翻译功能构建到常量本身中,所以这样的代码才有效:

namedtuple