如何在Python中表示枚举

How to represent enum in Python

本问题已经有最佳答案,请猛点这里访问。

我知道,我知道,这里和那里已经存在类似的问题了。但他们的问题和答案并不完全是我想要的。此外,他们是锁定问题,所以我不能给他们添加新的答案。 SMH。

首先,让我们澄清问题以了解其范围。在其他静态语言中使用enum时,如下所示:

1
2
3
4
5
6
7
public enum Size
{
    SMALL=0,
    MIDIUM=1,
    LARGE=2,
    BIG=2  // There can possibly be an alias
}

我们希望它能帮助我们:

  • 在引用值时防止拼写错误。例如,var foo = Size.SMALL有效,var bar = Size.SMAL应该生成糟糕的错误。
  • 枚举值可以支持字符串,例如HTTP404 ="Not Found", HTTP200 ="OK", ...。 (因此基于range(N)的那些实现是不可接受的。)
  • 将参数定义为特定的枚举类型时,它可以作为仅接受这种值的规则。例如,public void Foo(Size size) {...}
  • 我也希望我的Enum解决方案中的价值观成为一等公民。意思是,我的函数def parser(value_from_the_wire): ...想要使用一些本机值(例如整数或字符串等),而不是使用Enum成员。这是Python 3中标准Enum的棘手部分:

    • assert 2 == MY_ENUM.MY_VALUE仅在MY_ENUM派生自IntEnum时才有效(并且没有默认StrEnum,尽管自己创建子类并不困难)
    • 即使MY_ENUM来自IntEnumassert 2 in MY_ENUM也无效。

  • TL; DR:使用静脉

    因此,我的Python解决方案满足问题中的3个标准,它基于namedtuple,实现似乎比Python 3中新的内置Enum更直接。

    1
    2
    3
    4
    5
    6
    7
    8
    from collections import namedtuple

    def enum(name=None, **kwargs):
       """
        :param name: An optional type name, which only shows up when debugging by print(...)
       """

        # This actual implementation below is just a one-liner, even within 80-char
        return namedtuple(name or"Const_%d" % id(kwargs), kwargs.keys())(**kwargs)

    用法很简单。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # definition syntax
    SIZE = enum("Size", SMALL=0, MEDIUM=1, LARGE=2, BIG=2)

    # usage on referencing
    print(SIZE.SMALL)   # got 0, instead of <SIZE.SMALL: 0>
    try:
        print(SIZE.SMAL)    # got AttributeError
        assert False,"should not arrive this line"
    except AttributeError:
        pass

    # usage on comparison and contains-check
    assert SIZE.MEDIUM == 1  # works. It won't work when using standard Enum (unless using IntEnum)
    assert 1 in SIZE  # works. It won't work when using standard Enum (unless you wrote it as SIZE(1)).

    # usage on regulating input value
    def t_shirt_size(size):

        if size not in SIZE:
            raise ValueError("Invalid input value")

        print("Placing order with size: %s" % size)

    t_shirt_size(SIZE.MEDIUM)   # works
    t_shirt_size(2)             # also want this to work
    try:
        t_shirt_size(7)             # got AssertionError
        assert False,"This line should not be reached!"
    except ValueError:
        pass

    编辑1:我实际上意识到Python 3中有一个标准的Enum模块,从功能方面来讲,它主要是我下面的单行实现的超集。但是有一种情况是标准的Enum不适合我的需要。我希望这些价值观能够成为我的枚举中的一等公民;我希望我的t_shirt_size(...)函数接受实际值,而不仅仅是枚举成员。标准枚举方法不允许这两种用法:assert SIZE.MEDIUM == 1assert 1 in SIZE

    编辑2:鉴于人们倾向于将此主题刻板印象为重复,我计划实际将我的方法实现为具有大量文档的独立模块。我甚至提出了一个很酷的名字,venum,V代表价值。就在那个时候,我检查了pypi中的名字,发现已经有一个同名的包,使用与我相同的方法,并记录得很好。这样就解决了。我只是简单地点击安装静脉。 :-)


    使用Python 3的Enum实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from enum import IntEnum


    class SIZE(Enum):

        SMALL = 0
        MEDIUM = 1
        LARGE = 2
        BIG = 2

        @classmethod
        def contains(cls, value):
            return any([e.value == value for e in cls])

    和使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    print(SIZE.SMALL)   # got <SIZE.SMALL: 0>
    print(SIZE.SMAL)    # got AttributeError

    def t_shirt_size(size):
        assert size in SIZE,"Invalid input value"
        place_order_with_size(size)

    t_shirt_size(SIZE.MEDIUM)   # works
    t_shirt_size(7)             # got AssertionError