我想扩展Python枚举以支持未定义的值。
我的用例:我想从枚举中受益(例如,能够用语音名称来寻址/比较已知条目),但我也希望它支持未知值。如果该值后面没有名字,则str应该只打印该值的str表示,否则比较将失败。
让我给你一个简短的例子,我想要的东西:
from enum import Enum
class Foo(Enum):
A = 1
B = 2
C = 3
def __str__(self):
return self.name
print(Foo(1)) # prints 'A'
print(Foo(2)) # print 'B'
print(Foo(3)) # prints 'C'
print(Foo(1) == Foo.A) # prints 'true'
print(Foo(4)) # I'd expect '4'
print(Foo(123)) # I'd expect '123'
print(Foo(123) == Foo.A) # I'd expect False
最后几行当然会失败。
是否有扩展Enums的方法,或者也许还有另一种简单的pythonic方法? (请没有第三方图书馆。)
答案 0 :(得分:1)
对于您的用例,更自然的解决方案是:
例如:
known_values = {'a':1,'b':2,'c':1} # only one of 'a' and 'c' will be used
# 'a' guaranteed in 3.7+, implementation-specific before
# (https://stackoverflow.com/q/39980323)
extracted_values = (1,2,3,4,5)
known_values_reverse = {}
for k,v in known_values.items():
if v not in known_values_reverse:
known_values_reverse[v]=k
del k,v #if placed outside the loop, will error if known_values is empty
for v in extracted_values:
if v not in known_values_reverse:
known_values_reverse[v]=str(v)
del v
AutoFooEnum = enum.Enum(value='AutoFooEnum',names=(k,v for v,k in known_values_reverse.items()))
生成输出时,您需要使用AutoFooEnum(value).name
输出文本ID(如果有)或数字。
自然,由于数字不是有效的标识符,因此您将无法在代码中引用带有AutoFooEnum.<id>
的编号成员。但这看起来好像不是您需要的。
答案 1 :(得分:1)
要在多个Python值(尤其是Python 2)上实现此目的,您将需要一个外部库:aenum
1 。
from aenum import Enum, extend_enum
class Foo(Enum):
_order_ = 'A B C' # because Python 2 does not keep order
A = 1
B = 2
C = 3
def __str__(self):
return self.name
@classmethod
def _missing_(cls, value):
extend_enum(cls, str(value), (value, ))
return list(cls)[-1]
这会动态地将未知值添加为枚举成员并返回它。
1 披露:我是Python stdlib Enum
,enum34
backport和Advanced Enumeration (aenum
)库的作者。
答案 2 :(得分:1)
这是在Python 3.6,3.7和3.8中有效的另一个答案。
它涉及奇怪的元类黑客,所以要当心。
import enum
class TestMeta(enum.EnumMeta):
def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1):
if names is not None:
# the enum is being constructed via the functional syntax
return super().__call__(value, names=names, module=module, qualname=qualname, type=type, start=start)
try:
# attempt to get an enum member
return super().__call__(value, names=names, module=module, qualname=qualname, type=type, start=start)
except ValueError:
# no such member exists, but we don't care
return value
class Test(enum.Enum, metaclass=TestMeta):
A = 5
B = 6
print(Test(5), Test(6), Test(7))
此版本works in Python 2.7,但需要enum
的第三方库(请参见评论):
class TestMeta(enum.EnumMeta):
def __call__(cls, value, names=None, module=None, type=None, start=1):
if names is not None:
return enum.EnumMeta.__call__(cls, value, names, module, type, start)
try:
return enum.EnumMeta.__call__(cls, value, names, module, type, start)
except ValueError:
return value
答案 3 :(得分:1)
怎么样?
from enum import Enum
class CraftTypes(Enum):
wood_work = 0
welding = 1
mechanics = 2
unknown = 3
# 3 and above is unknown
@classmethod
def _missing_(cls, number):
return cls(cls.unknown)
简单而pythonic ...
答案 4 :(得分:0)
可以按照enum.Flag._create_pseudo_member_
中的示例尝试在运行时使用新成员扩展枚举:
from enum import Enum
class Foo(Enum):
A = 1
B = 2
C = 3
def __str__(self):
return self.name
@classmethod
def _missing_(cls, value):
if cls._member_type_ is object:
# construct a new singleton enum member
new_member = object.__new__(cls)
# include the value representation in _name_ because the Enum hash implementation relies on _name_
new_member._name_ = '%r' % value
new_member._value_ = value
# use setdefault in case another thread already created a composite with this value
new_member = cls._value2member_map_.setdefault(value, new_member)
return new_member
这种简单的实现方式将打破在类定义中将Enum
与其他类型混合的可能性,但是可以肯定地按照enum.EnumMeta.__new__
中的机制来解决。
@chepner已经评论过,这违反了诺言
定义枚举项后,Enum类才是最终的
(也可以在enum.EnumMeta.__new__
中找到),并且可以破坏某些Enum
方法(例如__len__
)的行为。