如何使用其他方法从现有dict创建Python枚举类?

2024-06-12 06:29:38 发布

您现在位置:Python中文网/ 问答频道 /正文

假设,我有一个预先存在的映射作为字典:

value_map = {'a': 1, 'b': 2}

我可以这样创建枚举类:

^{pr2}$

像这样使用它

a = MyEnum.a
print(a.value)
>>> 1
print(a.name)
>>> 'a'

但是我想为我的新enum类定义一些方法:

def double_value(self):
    return self.value * 2

当然,我可以这样做:

class MyEnum(Enum):
    a = 1
    b = 2
    @property
    def double_value(self):
        return self.value * 2

但正如我所说,我必须使用一个预定义的值映射字典,所以我不能这样做。 如何做到这一点?我试图从另一个类继承这个方法,就像mixin一样,但是我无法理解。在


Tags: 方法nameselfmapreturn字典定义value
2条回答

您可以创建一个从enum.EnumMeta(枚举的元类)派生的新元类(使用元元类或工厂函数,如下所示),并在创建类之前添加成员

import enum
import collections.abc


def enum_metaclass_with_default(default_members):
    """Creates an Enum metaclass where `default_members` are added"""
    if not isinstance(default_members, collections.abc.Mapping):
        default_members = enum.Enum('', default_members).__members__

    default_members = dict(default_members)

    class EnumMetaWithDefaults(enum.EnumMeta):
        def __new__(mcs, name, bases, classdict):
            """Updates classdict adding the default members and
            creates a new Enum class with these members
            """

            # Update the classdict with default_members
            # if they don't already exist
            for k, v in default_members.items():
                if k not in classdict:
                    classdict[k] = v

            # Add `enum.Enum` as a base class

            # Can't use `enum.Enum` in `bases`, because
            # that uses `==` instead of `is`
            bases = tuple(bases)
            for base in bases:
                if base is enum.Enum:
                    break
            else:
                bases = (enum.Enum,) + bases

            return super(EnumMetaWithDefaults, mcs).__new__(mcs, name, bases, classdict)

    return EnumMetaWithDefaults


value_map = {'a': 1, 'b': 2}


class MyEnum(metaclass=enum_metaclass_with_default(value_map)):
    @property
    def double_value(self):
        return self.value * 2


assert MyEnum.a.double_value == 2

另一种解决方案是直接尝试并更新locals(),因为当您试图赋值时,它会被一个创建枚举值的映射所取代。在

^{pr2}$

这似乎定义得足够好,a = 1很可能与{}相同,但在未来可能会发生变化。第一个解决方案更健壮,也不那么麻烦(我还没有在其他Python实现中测试过,但它的工作原理可能是一样的)

可以使用type参数将带有mixin方法的基类型传递到函数API中:

>>> import enum
>>> value_map = {'a': 1, 'b': 2}
>>> class DoubledEnum:
...     @property
...     def double_value(self):
...         return self.value * 2
...
>>> MyEnum = enum.Enum('MyEnum', value_map, type=DoubledEnum)
>>> MyEnum.a.double_value
2

{{1}你永远不能在cd2中创建一个完全使用的函数方法:

^{pr2}$

您也可以使用enum.EnumMeta()元类,就像Python创建class MyEnum(enum.Enum): ...子类时一样:

  1. 使用元类^{} hook创建类字典
  2. {7>在这里创建了基类(^),并在基类中传递了基类。在

enum.EnumMeta使用的自定义dictionary子类并不是为了方便重用而设计的;它实现了一个__setitem__钩子来记录元数据,但不会覆盖dict.update()方法,因此在使用value_map字典时,我们需要小心:

import enum

def enum_with_extras(name, value_map, bases=enum.Enum, **extras):
    if not isinstance(bases, tuple):
        bases = bases,
    if not any(issubclass(b, enum.Enum) for b in bases):
        bases += enum.Enum,
    classdict = enum.EnumMeta.__prepare__(name, bases)
    for key, value in {**value_map, **extras}.items():
        classdict[key] = value
    return enum.EnumMeta(name, bases, classdict)

然后将double_value=property(double_value)传递给该函数(连同枚举名和value_map字典):

>>> def double_value(self):
...     return self.value * 2
...
>>> MyEnum = enum_with_extras('MyEnum', value_map, double_value=property(double_value))
>>> MyEnum.a
<MyEnum.a: 1>
>>> MyEnum.a.double_value
2

否则,您可以创建不带成员的枚举的子类(任何descriptor都不是成员,因此函数、属性、类方法等等),因此您可以先定义不带成员的枚举:

class DoubledEnum(enum.Enum):
    @property
    def double_value(self):
        return self.value * 2

对于函数API(例如enum.Enum(..., type=DoubledEnum))和我编码为enum_with_extras()的元类方法,它都是可接受的基类。在

相关问题 更多 >