如何扩展 Python 枚举?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33679930/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 13:45:50  来源:igfitidea点击:

How to extend Python Enum?

pythonpython-3.xenums

提问by falek.marcin

What is best practice for extending Enumtype in Python 3.4 and is there even a possibility for do this?

Enum在 Python 3.4 中扩展类型的最佳实践是什么,甚至有可能这样做吗?

For example:

例如:

from enum import Enum

class EventStatus(Enum):
   success = 0
   failure = 1

class BookingStatus(EventStatus):
   duplicate = 2
   unknown = 3

Traceback (most recent call last):
...
TypeError: Cannot extend enumerations

Currently there is no possible way to create a base enum class with members and use it in other enum classes (like in the example above). Is there any other way to implement inheritance for Python enums?

目前没有可能的方法来创建具有成员的基本枚举类并在其他枚举类中使用它(如上面的示例)。有没有其他方法可以实现 Python 枚举的继承?

采纳答案by GingerPlusPlus

Subclassing an enumeration is allowed only if the enumeration does not define any members.

Allowing subclassing of enums that define members would lead to a violation of some important invariants of types and instances.

仅当枚举未定义任何成员时,才允许对枚举进行子类化。

允许定义成员的枚举的子类化将导致违反一些重要的类型和实例不变量。

https://docs.python.org/3/library/enum.html#restricted-subclassing-of-enumerations

https://docs.python.org/3/library/enum.html#restricted-subclassing-of-enumerations

So no, it's not directly possible.

所以,这不是直接可能的。

回答by Ethan Furman

While uncommon, it is sometimes useful to create an enum from many modules. The aenum1library supports this with an extend_enumfunction:

虽然不常见,但有时从许多模块创建枚举很有用。在1个库支持这与功能:aenumextend_enum

from aenum import Enum, extend_enum

class Index(Enum):
    DeviceType    = 0x1000
    ErrorRegister = 0x1001

for name, value in (
        ('ControlWord', 0x6040),
        ('StatusWord', 0x6041),
        ('OperationMode', 0x6060),
        ):
    extend_enum(Index, name, value)

assert len(Index) == 5
assert list(Index) == [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode]
assert Index.DeviceType.value == 0x1000
assert Index.StatusWord.value == 0x6041


1Disclosure: I am the author of the Python stdlib Enum, the enum34backport, and the Advanced Enumeration (aenum)library.

1披露:我是Python stdlibEnumenum34backportAdvanced Enumeration ( aenum)库的作者。

回答by Jul3k

Calling the Enum class directly and making use of chain allows the extension (joining) of an existing enum.

直接调用 Enum 类并使用链允许扩展(加入)现有的枚举。

I came upon the problem of extending enums while working on a CANopen implementation. Parameter indices in the range from 0x1000 to 0x2000 are generic to all CANopen nodes while e.g. the range from 0x6000 onwards depends open whether the node is a drive, io-module, etc.

我在处理 CANopen 实现时遇到了扩展枚举的问题。范围从 0x1000 到 0x2000 的参数索引适用于所有 CANopen 节点,而例如,从 0x6000 开始的范围取决于打开节点是否为驱动器、io 模块等。

nodes.py:

节点.py:

from enum import IntEnum

class IndexGeneric(IntEnum):
    """ This enum holds the index value of genric object entrys
    """
    DeviceType    = 0x1000
    ErrorRegister = 0x1001

Idx = IndexGeneric

drives.py:

驱动器.py:

from itertools import chain
from enum import IntEnum
from nodes import IndexGeneric

class IndexDrives(IntEnum):
    """ This enum holds the index value of drive object entrys
    """
    ControlWord   = 0x6040
    StatusWord    = 0x6041
    OperationMode = 0x6060

Idx= IntEnum('Idx', [(i.name, i.value) for i in chain(IndexGeneric,IndexDrives)])

回答by John Crawford

I've opted to use a metaclass approach to this problem.

我选择使用元类方法来解决这个问题。

from enum import EnumMeta

class MetaClsEnumJoin(EnumMeta):
    """
    Metaclass that creates a new `enum.Enum` from multiple existing Enums.

    @code
        from enum import Enum

        ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
        ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
        class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
            pass

        print(ENUMJOINED.a)
        print(ENUMJOINED.b)
        print(ENUMJOINED.c)
        print(ENUMJOINED.d)
    @endcode
    """

    @classmethod
    def __prepare__(metacls, name, bases, enums=None, **kargs):
        """
        Generates the class's namespace.
        @param enums Iterable of `enum.Enum` classes to include in the new class.  Conflicts will
            be resolved by overriding existing values defined by Enums earlier in the iterable with
            values defined by Enums later in the iterable.
        """
        #kargs = {"myArg1": 1, "myArg2": 2}
        if enums is None:
            raise ValueError('Class keyword argument `enums` must be defined to use this metaclass.')
        ret = super().__prepare__(name, bases, **kargs)
        for enm in enums:
            for item in enm:
                ret[item.name] = item.value  #Throws `TypeError` if conflict.
        return ret

    def __new__(metacls, name, bases, namespace, **kargs):
        return super().__new__(metacls, name, bases, namespace)
        #DO NOT send "**kargs" to "type.__new__".  It won't catch them and
        #you'll get a "TypeError: type() takes 1 or 3 arguments" exception.

    def __init__(cls, name, bases, namespace, **kargs):
        super().__init__(name, bases, namespace)
        #DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older.  You'll get a
        #"TypeError: type.__init__() takes no keyword arguments" exception.

This metaclass can be used like so:

这个元类可以像这样使用:

>>> from enum import Enum
>>>
>>> ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
>>> ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
>>> class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
...     e = 5
...     f = 6
...
>>> print(repr(ENUMJOINED.a))
<ENUMJOINED.a: 1>
>>> print(repr(ENUMJOINED.b))
<ENUMJOINED.b: 2>
>>> print(repr(ENUMJOINED.c))
<ENUMJOINED.c: 3>
>>> print(repr(ENUMJOINED.d))
<ENUMJOINED.d: 4>
>>> print(repr(ENUMJOINED.e))
<ENUMJOINED.e: 5>
>>> print(repr(ENUMJOINED.f))
<ENUMJOINED.f: 6>

Note what happens in the event of a namespace conflict:

注意命名空间冲突时会发生什么:

>>> ENUMC = Enum('ENUMA', {'a': 1, 'b': 2})
>>> ENUMD = Enum('ENUMB', {'a': 3})
>>> class ENUMJOINEDCONFLICT(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMC, ENUMD)):
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 19, in __prepare__
  File "C:\Users\jcrwfrd\AppData\Local\Programs\Python\Python37\lib\enum.py", line 100, in __setitem__
    raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'a'
>>>

This is due to the base enum.EnumMeta.__prepare__returning a special enum._EnumDictinstead of the typical dictobject that behaves different upon key assignment. You may wish to suppress this error message by surrounding it with a try-except TypeError, or there may be a way to modify the namespace before calling super().__prepare__(...).

这是因为基enum.EnumMeta.__prepare__返回一个特殊的enum._EnumDict而不是dict在键分配时表现不同的典型对象。您可能希望通过用try-包围它来抑制此错误消息except TypeError,或者可能有一种方法可以在调用之前修改命名空间super().__prepare__(...)

This approach creates a new Enumusing the same name-value pairs as the source Enums, but the resulting Enummembers are unique. The names and values will be the same, but they will still fail certain comparisons:

这种方法Enum使用与源Enums相同的名称-值对创建一个新的,但结果Enum成员是唯一的。名称和值将相同,但它们仍会在某些比较中失败:

>>> ENUMA.b.name == ENUMJOINED.b.name
True
>>> ENUMA.b.value == ENUMJOINED.b.value
True
>>> ENUMA.b == ENUMJOINED.b
False
>>> ENUMA.b is ENUMJOINED.b
False
>>>

回答by foolcage

I think you could do it in this way:

我认为你可以这样做:

import enum
from typing import List
from enum import Enum

def extend_enum(current_enum, names: List[str], values: List = None):
    if not values:
        values = names

    for item in current_enum:
        names.append(item.name)
        values.append(item.value)

    return enum.Enum(current_enum.__name__, dict(zip(names, values)))

class EventStatus(Enum):
   success = 0
   failure = 1

class BookingStatus(object):
   duplicate = 2
   unknown = 3

BookingStatus = extend_enum(EventStatus, ['duplicate','unknown'],[2,3])

the key points is:

关键点是:

  • python could change anything at runtime
  • class is object too
  • python可以在运行时改变任何东西
  • 类也是对象

回答by jhpx

Another way :

其它的办法 :

Letter = Enum(value="Letter", names={"A": 0, "B": 1})
LetterExtended = Enum(value="Letter", names=dict({"C": 2, "D": 3}, **{i.name: i.value for i in Letter}))

Or :

或者 :

LetterDict = {"A": 0, "B": 1}
Letter = Enum(value="Letter", names=LetterDict)

LetterExtendedDict = dict({"C": 2, "D": 3}, **LetterDict)
LetterExtended = Enum(value="Letter", names=LetterExtendedDict)

Output :

输出 :

>>> Letter.A
<Letter.A: 0>
>>> Letter.C
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "D:\jhpx\AppData\Local\Programs\Python\Python36\lib\enum.py", line 324, in __getattr__
    raise AttributeError(name) from None
AttributeError: C
>>> LetterExtended.A
<Letter.A: 0>
>>> LetterExtended.C
<Letter.C: 2>