【问题标题】:How to represent enum in Python [duplicate]如何在 Python 中表示枚举 [重复]
【发布时间】:2019-02-28 20:15:30
【问题描述】:

我知道,我知道,herethere 已经有类似的问题了。但他们的问题和答案并不是我想要的。此外,它们是锁定问题,因此我无法为它们添加新答案。 SMH。

首先,让我们澄清问题以了解其范围。在像这样在其他静态语言中使用枚举时:

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

我们希望它能帮助我们:

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

    • assert 2 == MY_ENUM.MY_VALUE 仅在 MY_ENUM 派生自 IntEnum 时才有效(并且没有默认的 StrEnum,尽管自己子类化并不困难)
    • assert 2 in MY_ENUM 不起作用,即使 MY_ENUM 派生自 IntEnum

【问题讨论】:

  • 我不明白您的问题与您链接的问题有何不同。为什么那里的答案不回答你的问题?
  • @Aran-Fey 这些问题都没有明确要求,因此,他们的答案往往基于不同回答者心中的不同假设。
  • 嗯?有什么要澄清的?我认为“我如何表示枚举”这个问题很清楚。你的枚举能做什么而现有答案中的其他枚举不能?
  • 好吧,当我发布这个问答时,至少我做了功课。你在发布你的 cmets 之前做了你的吗?大多数天真的解决方案class MY_ENUM: NAME1 = "value1" 不满足我的要求#3,即允许if input_value in MY_ENUM: ... 检查。其他一些人假设“如果您需要数值,这是最快的方法:dog, cat, rabbit = range(3)”,这不是我想要的。等等等等。
  • 你是认真的吗?您列出的所有问题都与该问题无关。只有当您选择了一种不好的方式来表示您的枚举时,这些问题才会存在。所有这些都是答案的问题,而不是问题。如果您看到错误的答案,请投反对票。您的问题与其他问题没有任何不同。

标签: python enums


【解决方案1】:

TL;DR:使用venum

所以我的 Python 解决方案要满足问题中的 3 个标准,它基于 namedtuple,并且实现似乎比新的 built-in Enum in Python 3 更简单。

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)

现在使用很简单。

# 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:我实际上知道有一个 standard Enum module in Python 3,从功能上讲,它在很大程度上是我下面的单行实现的超集。但是,有一种情况是标准 Enum 不适合我的需要。我希望这些价值观在我的枚举中成为一等公民;我希望我的 t_shirt_size(...) 函数接受一个真正的值,而不仅仅是一个枚举成员。标准枚举方法不允许这两种用法:assert SIZE.MEDIUM == 1assert 1 in SIZE

编辑 2:鉴于人们倾向于将此主题定型为重复,我计划将我的方法实际实现为具有大量文档的独立模块。我什至给它起了一个很酷的名字,venum,V 代表价值。就在那个时候,我在pypi中查看了名字,发现已经有一个同名的包,使用与我相同的方法,documented well。这样就解决了。我会简单地pip install venum 代替。 :-)

【讨论】:

  • enum.Enum 不会强迫你让你的函数接受枚举成员。使用 Size 作为 enum.Enum 枚举,您可以接受 size 作为 int 并调用 Size(size) 以获取枚举成员。
  • @user2357112,谢谢,很高兴知道标准枚举中有这样一个语法糖:SIZE(value) 等同于if value not in SIZE: raise ValueError("...")。我个人认为后者更pythonic,因为"Explicit is better than implicit".
  • @RayLuo:一切都有它的位置——例如,一个不会说if my_key not in some_dict: raise KeyError(),即使它比some_dict[my_key]更明确。
  • @EthanFurman:好吧,如果“一切都有它的位置”,你为什么不允许不同风格的答案有它的位置?
【解决方案2】:

用 Python 3 的 Enum 实现:

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])

并使用:

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

【讨论】:

  • 感谢您的反对。我实际上知道 Python 3 中有一个标准的 Enum 模块,从功能上讲,它很大程度上是我的单行实现的超集。但是,有一种情况不适合我的需要。我希望这些价值观在我的枚举中成为一等公民;我希望我的 t_shirt_size(...) 函数接受一个真正的值,而不仅仅是一个枚举成员。标准枚举方法不允许这两种用法:assert SIZE.MEDIUM == 1assert 1 in SIZE。现在,您的特定答案不适合我的需要。但我不会投反对票。你也公平吗?
  • @RayLuo: assert SIZE.MEDIUM == 1 确实可以与IntEnum 一起使用。第二个不能按原样工作,但您可以添加自己的方法来完成繁重的工作。
  • @RayLuo:您的部分答案是错误的——这就是我否决它的原因。我的回答没有错,现在可以满足您直接进行价值比较和遏制检查的需要。
  • 即使assert SIZE.MEDIUM == 1 确实与IntEnum 一起使用,我已经知道并在我的问题中指出了这一点;我的一个用例是也支持字符串,并且没有默认的StrEnum,我已经再次指出了这一点。我只是在寻找一种将价值观简单地视为一等公民的方法,我找到了我需要的解决方案,所以根据定义,我的答案是正确的,我只是谦虚地不(还?)接受它,以鼓励不同的声音。现在,你有多大信心说我的答案是错误的(对我而言)而你的答案是正确的(对我而言)?抱歉,我不这么认为。
  • @RayLuo:正如我在评论中所说,您的回答有部分是不正确的——就像在技术上不正确一样。解决这些问题,我将删除我的反对票。
猜你喜欢
  • 2012-04-20
  • 1970-01-01
  • 1970-01-01
  • 2020-12-07
  • 2014-02-02
  • 2014-08-18
  • 2019-05-01
  • 2023-04-01
  • 1970-01-01
相关资源
最近更新 更多