【问题标题】:How do I keep track of Python enum values internally?如何在内部跟踪 Python 枚举值?
【发布时间】:2019-04-02 03:06:30
【问题描述】:

我的最终目标实际上是在我的 Enum 类中创建一个辅助方法,该方法始终返回一个 Enum 成员并且从不引发异常,给定任何可能的值,例如

Color.from_value('red')

如果值不是枚举的一部分,辅助方法将返回一个默认值,例如Color.UNKNOWN

基于这个answer from another related question,我可以通过一些内置成员列出这些值来检查它们。但是,我接下来要做的是跟踪内部成员中的所有值,这样我就不必每次调用辅助方法时都遍历这些值。我尝试做类似以下的事情:

class Color(Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'

    # this doesn't work
    _values = [item.value for item in Color]

正如预期的那样,它不起作用。这可能是 Python 枚举中已经内置的东西吗?

【问题讨论】:

  • 如果有人打电话给Color.from_value('chucknorris')会怎样?这会创建一个新的 Enum 值并返回它吗?
  • 不。它将返回一个默认枚举,类似于Color.UNKNOWN

标签: python python-3.x enums


【解决方案1】:

您可以创建方法并检查类中的值:

import enum

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @classmethod
    def from_value(cls, value):
      try:
        return {item.value: item for item in cls}[value]
      except KeyError:
        return cls.UNKNOWN

print(Color.from_value("hey"))
print(Color.from_value("red"))

结果:

Color.UNKNOWN
Color.RED

你有一个live example

如果你不想重复,你总是可以有一个值的外部缓存:

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @classmethod
    def from_value(cls, value):
      try:
        return _COLORITEMS[value]
      except KeyError:
        return cls.UNKNOWN
_COLORITEMS = {item.value: item for item in Color}

【讨论】:

  • 哈!正要提出第二种变体。但你更快!
  • 那么,从技术上讲,没有办法在内部跟踪 Enum 类中的值?
  • @PsychoPunch,我不知道怎么做,因为在完全读取之前您无法访问类值(AFAIK)
【解决方案2】:

此后我接受了Netwave's answer,但我将其添加为与原始版本的一些变化。我想保留答案以供参考。

import enum

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @staticmethod
    def from_value(value):
      try:
        return Color(value)
      except ValueError:
        return cls.UNKNOWN

请注意,在这种方法中,我已将 @classmethod 更改为 @staticmethod,因为我不再需要 cls 参数。此外,我在这里处理ValueError 而不是KeyError,因为这是Color() 在找不到值的情况下引发的。这也适用于多值/元组值枚举。

至于第二种方法:

import enum

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @staticmethod
    def from_value(value):
        return Color(value) if value in _COLORITEMS else Color.UNKNOWN

_COLORITEMS = [item.value for item in Color]

在这里,我从 dict 切换到 list 以跟踪值。我没有处理KeyError,而是简单地检查了该值是否在列表中。我本可以使用一个集合,但因为它是一个枚举,所以值保证是唯一的(如果我理解正确的话)。

【讨论】:

  • classmethod 在这里是更好的选择,因为该方法确实需要类才能正常工作。您通过直接指定类名来回避这个问题,但是一旦您更改名称Color,这将失败。使用 staticmethod 会产生误导。
【解决方案3】:

您可以在内部跟踪值,但这有点麻烦:

尝试 1

_values = [k for k in vars().keys() if not k.startswith('_')]

问题 1

>>> # _values is a member
>>> Color._values
<Color._huh: ['RED', 'BLUE', 'GREEN']>

尝试 2

使用来自this answerConstant

_values = Constant([k for k in vars().keys() if not k.startswith('_')])

问题 2

它并不是真正恒定的,因为您仍然可以附加到列表中——但这可以通过转换为 tuple 来解决:

_values = Constant(tuple([k for k in vars().keys() if not k.startswith('_')]))

但是,这仍然不能解决UNKNOWN 的问题。

解决方案

使用 Python 3.6 或 aenum 2.01,您可以指定一个 _missing_ 方法,该方法将被调用以在引发 ValueError 之前为您的班级提供最后一次机会:

class Constant:  # use Constant(object) if in Python 2
    def __init__(self, value):
        self.value = value
    def __get__(self, *args):
        return self.value
    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.value)


class Color(Enum):

    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = 'unknown'
    _values = Constant(tuple([k for k in vars().keys() if not k.startswith('_')]))

    @classmethod
    def _missing_(cls, name):
        if name.lower() in cls._values:
            return cls(name.lower())
        else:
            return cls.UNKNOWN

class Color(Enum):

    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = 'unknown'

    @classmethod
    def _missing_(cls, name):
        if name == name.lower():
            # avoid infinite recursion
            return cls.UNKNOWN
        else:
            return cls(name.lower())

注意:只有枚举成员或 None 应该由 _missing_ 返回——如果返回任何其他内容,以后的 Python 版本将引发 TypeError


1 披露:我是Python stdlib Enumenum34 backportAdvanced Enumeration (aenum) 库的作者。

【讨论】:

  • 谢谢,但我不确定您为什么将其标记为重复,因为我的问题实际上是关于其他问题。虽然您的解决方案可能适用于两者,并且问题交叉很多,但它们并不完全相同。看,我还有一个要求,from_value 不区分大小写,以便from_value('red')from_value('Red') 返回Color.RED。我如何使用_missing_ 来实现这一点?
猜你喜欢
  • 2011-12-28
  • 1970-01-01
  • 2010-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-13
  • 2018-01-22
  • 1970-01-01
相关资源
最近更新 更多