如何扩展 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
How to extend Python Enum?
提问by falek.marcin
What is best practice for extending Enum
type 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 aenum
1library supports this with an extend_enum
function:
虽然不常见,但有时从许多模块创建枚举很有用。在1个库支持这与功能:aenum
extend_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 enum34
backport, and the Advanced Enumeration (aenum
)library.
1披露:我是Python stdlibEnum
、enum34
backport和Advanced 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._EnumDict
instead of the typical dict
object 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 Enum
using the same name-value pairs as the source Enum
s, but the resulting Enum
members are unique. The names and values will be the same, but they will still fail certain comparisons:
这种方法Enum
使用与源Enum
s相同的名称-值对创建一个新的,但结果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>