【问题标题】:Default or invalid value for enumerator枚举器的默认值或无效值
【发布时间】:2020-03-27 09:37:12
【问题描述】:

在 Python 中,我一直在使用 enum 模块创建枚举。通常用int-version来允许转换:

from enum import IntEnum
class Level(IntEnum):
    DEFAULTS    = 0
    PROJECT     = 1
    MASTER      = 2
    COLLECT     = 3
    OBJECT      = 4

我想为这种类型的变量提供某种类型的无效或未定义值。例如,如果一个对象已初始化并且它的级别当前未知,我想通过执行类似self.level = Level.UNKNOWNLevel.INVALIDLevel.NONE 之类的操作来创建它。我通常将这些特殊值的内部值设置为-1

我一直遇到的问题类型是添加任何像这样的特殊值会中断迭代和len() 调用。例如,如果我想生成一些列表来保存每个级别类型list = [x] * len(Level),它会将这些额外的值添加到列表长度中,除非我手动从中减去 1。或者如果我迭代了关卡类型for lvl in Level:,我将不得不手动跳过这些特殊值。

所以我想知道是否有任何聪明的方法来解决这个问题?在 Python 中创建这样的无效值是否毫无意义?我应该改用全局None 之类的东西吗?或者有什么方法可以定义枚举器的invalid 表示,这样它就不会被包含在迭代或长度逻辑中?

【问题讨论】:

  • 等等,为什么你使用IntEnum?你真的不应该这样做,除非你想保持与 enum 模块可用之前编写的旧代码的向后兼容性。您是否阅读了文档中的警告?
  • 我对 Python 不是很有经验,所以它对我来说是全新的。我应该使用什么?

标签: python enums


【解决方案1】:

惯用地说,当您使用枚举器时,是因为您毫无疑问地知道所有事物都将属于枚举类别之一。有一个包罗万象的“其他”或“无”类别很常见。

如果项目的级别在创建时未知,那么您可以实例化具有“未知”级别的所有对象,除非您为其提供另一个级别。

您在内部使用 -1 值处理这些是否有特殊原因?这些级别是错误的,还是“未知”级别有效?

【讨论】:

  • 它用于链接到可变级别项目的状态对象。因此,它在 未链接 时为 unknown 是有效的,但在 链接 到某个项目时无效。但是,枚举之外存在与值相关的数据,并且unknown 没有有效数据。如果有意义,我可以添加一些,但它永远不会被使用(故意)。
【解决方案2】:

这个问题的答案类似于Adding NONE and ALL to Flag Enums 的答案(请随意查看那里的深入解释;注意:该答案使用类类型的装饰器,而下面是一个函数-type 装饰器)。

def add_invalid(enumeration):
    """
    add INVALID psuedo-member to enumeration with value of -1
    """
    #
    member = int.__new__(enumeration, -1)
    member._name_ = 'INVALID'
    member._value_ = -1
    enumeration._member_map_['INVALID'] = member
    enumeration._value2member_map_[-1] = member
    return enumeration

看起来像

@add_invalid
class Level(IntEnum):
    DEFAULTS    = 0
    PROJECT     = 1
    MASTER      = 2
    COLLECT     = 3
    OBJECT      = 4

并在使用中:

>>> list(Level)
[<Level.DEFAULTS: 0>, <Level.PROJECT: 1>, <Level.MASTER: 2>, <Level.COLLECT: 3>, <Level.OBJECT: 4>]

>>> type(Level.INVALID)
<enum 'Level'>

>>> Level.INVALID
<Level.INVALID: -1>

>>> Level(-1)
<Level.INVALID: -1>

>>> Level['INVALID']
<Level.INVALID: -1>

此方法有几个注意事项:

  • 它使用的内部枚举结构可能会在未来发生变化
  • INVALID,虽然没有正常显示,但在其他方面是 Enum 成员(因此无法更改、删除等)

如果您不想使用内部结构,和/或您实际上不需要 INVALID 成为 Enum 成员,则可以改用 Constantfound here

class Constant:
    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 Level(IntEnum):
    #
    DEFAULTS    = 0
    PROJECT     = 1
    MASTER      = 2
    COLLECT     = 3
    OBJECT      = 4
    #
    INVALID = Constant(-1)

并在使用中:

>>> Level.INVALID
-1

>>> type(Level.INVALID)
<class 'int'>

>>> list(Level)
[<Level.DEFAULTS: 0>, <Level.PROJECT: 1>, <Level.MASTER: 2>, <Level.COLLECT: 3>, <Level.OBJECT: 4>]

使用自定义描述符的缺点是它可以在类上进行更改;你可以通过使用aenum1 及其内置的constant 类(NB:小写)来解决这个问题:

from aenum import IntEnum, constant

class Level(IntEnum):
    #
    DEFAULTS    = 0
    PROJECT     = 1
    MASTER      = 2
    COLLECT     = 3
    OBJECT      = 4
    #
    INVALID = constant(-1)

并在使用中:

>>> Level.INVALID
-1

>>> Level.INVALID = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/ethan/.local/lib/python3.6/site-packages/aenum/__init__.py", line 2128, in __setattr__
    '%s: cannot rebind constant %r' % (cls.__name__, name),
AttributeError: Level: cannot rebind constant 'INVALID'

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

【讨论】:

  • 三合一。感谢您提供的所有信息。我要看看这两个,但我想我喜欢第二个(和第三个)。你能帮我理解你所说的“它可以在课堂上改变”的意思吗?您的意思是它不是恒定的吗?
  • @Robert:没错。在正常情况下,最后的分配 (Level.INVALID = ...) 会成功,但 aenum.Enum 元类会阻止它。
猜你喜欢
  • 2011-10-27
  • 1970-01-01
  • 1970-01-01
  • 2011-10-14
  • 1970-01-01
  • 1970-01-01
  • 2016-10-10
  • 2011-07-23
  • 1970-01-01
相关资源
最近更新 更多