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来自IntEnum,assert 2 in MY_ENUM也无效。
-
我不明白你的问题与你所链接的问题有什么不同。为什么那里的答案没有回答你的问题?
-
@Aran-Fey这些问题都没有澄清要求,因此,他们的答案倾向于基于不同回答者心中的不同假设。
-
咦?有什么要澄清的?我认为,"我如何代表一个枚举"的问题非常清楚。你的枚举在现有答案中的其他枚举不能做什么?
-
好吧,当我发布这个Q& A时,至少我完成了我的作业。在发表评论之前,你做过你的吗?大多数天真的解决方案class MY_ENUM: NAME1 ="value1"不满足我的要求#3,即允许if input_value in MY_ENUM: ...检查。其他一些人有一个假设"如果你需要数值,这是最快的方法:dog, cat, rabbit = range(3)",这不是我想要的。等等
-
你是认真的吗?您列出的所有问题都与问题无关。如果你选择一种不好的方式来表示你的枚举,那么这些问题才会存在。所有这些都是答案的问题,而不是问题。如果你看到一个错误的答案,请进行投票。您的问题与其他问题没有任何不同。
-
所以当你在这里写下你的第一条评论时,你会问"为什么那里的答案不能回答我的问题"。现在你似乎知道为什么,然后你的建议是要求我对所有这些答案进行投票,因为它们碰巧不符合我的需要? 2.我还解释说,否则我希望在这些问题上添加我的答案,但是他们被锁定了。 3.如果我的问题清楚地阐明了潜在的要求,那么它不会为他人提供额外的价值吗?你为什么遇到这个问题?嗯,实际上,我真的不需要知道你对它们的看法。照顾自己。
-
那里的一些答案完美地回答了你的问题。只有坏人不会。 2)好的。我认为仅仅因为您想要为锁定的问题添加答案而创建重复的问题是不合适的。 3)如果您觉得需要澄清要求,为什么不能编辑现有问题?
-
我不同意。这些现有问题已经以非常广泛的方式提出,然后从那时起积累了大量的教育答案。只是他们不符合我在这里描述的具体情况。此外,在这一点上,通过缩小其范围来改变他们的问题是不合适的,然后隐含地使许多现有的答案变得无关紧要。最后但并非最不重要的是,一个特定的问题通过不是一个过于宽泛的问题来增加价值。所以它不是重复的。
-
"我也希望这些价值观在我的Enum解决方案中成为一等公民" - 但是enum.Enum就是这样做的。 enum.Enum值是一流的,功能齐全的对象。您可以在它们上定义方法,您可以将它们与普通的整数等区分开来。您的详细说明和您的自我回答表明您希望您的枚举仅仅是普通整数和字符串的别名集合。
-
如果你想从Size.SMALL转到0,反之亦然,那么enum.Enum使用Size.SMALL.value和Size(0)就已经可以了。
-
@RayLuo:你的4a有两个错误:1)属性是.value,而不是.MY_VALUE; 2)assert 2 == MY_ENUM.value只要值为'2'(不是3,而不是'红色'等)就可以工作 - 无论MY_ENUM是Enum还是IntEnum。
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 == 1或assert 1 in SIZE。
编辑2:鉴于人们倾向于将此主题刻板印象为重复,我计划实际将我的方法实现为具有大量文档的独立模块。我甚至提出了一个很酷的名字,venum,V代表价值。就在那个时候,我检查了pypi中的名字,发现已经有一个同名的包,使用与我相同的方法,并记录得很好。这样就解决了。我只是简单地点击安装静脉。 :-)
-
enum.Enum不会强制您让您的函数接受枚举成员。使用Size作为enum.Enum枚举,您可以接受Size作为int并调用Size(size)来获取枚举成员。
-
@ user2357112,谢谢,很高兴知道标准枚举中存在这样的语法糖:SIZE(value)等同于if value not in SIZE: raise ValueError("...")。我个人认为后者更像是pythonic,因为"明确比隐含更好"。
-
@RayLuo:一切都有它的位置 - 例如,一个不会说if my_key not in some_dict: raise KeyError(),即使它比some_dict[my_key]更明确。
-
@EthanFurman:嗯,如果"一切都有它的位置",你为什么不允许不同的风格答案来取代它?
使用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 |
-
谢谢你的投票。我实际上意识到Python 3中有一个标准的Enum模块,从功能上讲,它基本上是我的单行实现的超集。但是有一种情况不符合我的需要。我希望这些价值观能够成为我的枚举中的一等公民;我希望我的t_shirt_size(...)函数接受一个真正的值,而不仅仅是一个枚举成员。标准枚举方法不允许这两种用法:assert SIZE.MEDIUM == 1或assert 1 in SIZE。现在,您的具体答案不符合我的需要。但我不打算投它。你也公平吗?
-
@RayLuo:assert SIZE.MEDIUM == 1可以使用IntEnum。第二个不能按原样运行,但你可以添加自己的方法来完成繁重的工作。
-
@RayLuo:你的答案部分是错误的 - 这就是我投票的原因。我的答案中没有任何内容是错误的,现在它满足了您对直接价值比较和遏制检查的需求。
-
即使assert SIZE.MEDIUM == 1与IntEnum一起使用,我已经知道并在我的问题中指出了IntEnum;我的一个用例是也支持字符串,并且没有默认的StrEnum,我已经指出了这一点。我只是想找到一种简单地将价值视为一等公民的方法,我找到了一个我需要的解决方案,所以根据定义,我的答案是正确的,我只是谦虚地(但是?)接受它,鼓励不同的声音。现在,你有信心说我的回答是错的(对我而言)你的回答是对的(对我而言)?对不起,我不这么认为。
-
@RayLuo:正如我在评论中所说,部分答案是错误的 - 因为技术上不正确。解决这些问题,我将删除我的downvote。