簡體   English   中英

如何使用其他方法從現有字典創建 Python Enum class?

[英]How to create Python Enum class from existing dict with additional methods?

比方說,我有一個預先存在的映射作為字典:

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

我可以這樣創建一個枚舉 class:

from enum import Enum
MyEnum = Enum('MyEnum', value_map)

並像這樣使用它

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

但是后來我想為我的新枚舉 class 定義一些方法:

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

當然,我可以這樣做:

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

但正如我所說,我必須使用預定義的值映射字典,所以我不能這樣做。 如何實現? 我試圖從另一個 class 繼承定義這個方法就像一個 mixin,但我無法弄清楚。

您可以使用mixin方法將基類型傳遞到函數API中,並使用type參數:

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

對於從不使用class語句的全功能方法,您可以使用type()函數創建基本混合:

DoubledEnum = type('DoubledEnum', (), {'double_value': property(double_value)})
MyEnum = enum.Enum('MyEnum', value_map, type=DoubledEnum)

您也可以使用相同的方式使用enum.EnumMeta()元類,就像Python創建class MyEnum(enum.Enum): ...子類:

  1. 使用元類__prepare__鈎子創建一個類字典
  2. 調用元類,傳入類名, (enum.Enum,)(enum.Enum,) here)和在步驟1中創建的類字典。

enum.EnumMeta使用的自定義字典子類並非真正設計用於易於重用; 它實現了一個__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

否則,您可以創建沒有成員的枚舉的子類( 描述符不是成員的任何內容,因此函數,屬性,類方法等),因此您可以先定義一個沒有成員的枚舉:

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

這是功能API(例如enum.Enum(..., type=DoubledEnum) )和我編碼為enum_with_extras()的元類方法的可接受基類。

您可以創建一個新的元類(使用元元類或工廠函數,如下所示),它派生自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() ,因為當您嘗試分配值時,它會替換為創建枚舉值的映射。

import enum


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


def set_enum_values(locals, value_map):
    # Note that we can't use `locals.update(value_map)`
    # because it's `locals.__setitem__(k, v)` that
    # creates the enum value, and `update` doesn't
    # call `__setitem__`.
    for k, v in value_map:
        locals[k] = v


class MyEnum(enum.Enum):
    set_enum_values(locals(), value_map)

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


assert MyEnum.a.double_value == 2

這似乎已經足夠明確, a = 1很可能與locals()['a'] = 1 ,但未來可能會發生變化。 第一個解決方案更強大,更少hacky(我沒有在其他Python實現中測試它,但它可能工作相同)

在@Artyer 的回答中添加更多內容(骯臟的技巧)。

請注意,如果您從dict創建Enum ,則還可以為 Enum 提供“附加”功能,請參閱...

from enum import Enum

_colors = {"RED": (1, "It's the color of blood."), "BLUE": (2, "It's the color of the sky.")}


def _set_members_colors(locals: dict):
    for k, v in colors.items():
        locals[k] = v[0]


class Colors(int, Enum):

    _set_members_colors(locals())

    @property
    def description(self):
        return colors[self.name][1]

print(str(Colors.RED))
print(str(Colors.RED.value))
print(str(Colors.RED.description))

Output...

Colors.RED
1
It's the color of blood.

謝謝!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM