在Python枚举中支持未知值

时间:2019-05-05 21:26:11

标签: python inheritance enums

我想扩展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方法? (请没有第三方图书馆。)

5 个答案:

答案 0 :(得分:1)

对于您的用例,更自然的解决方案是:

  • 获取输入中存在的数字值的完整列表(通过解析)
  • 使用这些值和已知标识符使用Functional API自动生成Enum类

例如:

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 Enumenum34 backportAdvanced Enumeration (aenum)库的作者。

答案 2 :(得分:1)

这是在Python 3.6,3.73.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__)的行为。