在Python 3.4中扩展Enum
类型的最佳做法是什么?是否有可能这样做?
例如:
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
目前没有办法用成员创建基本枚举类,并在其他枚举类中使用它(如上例所示)。有没有其他方法可以实现Python枚举的继承?
答案 0 :(得分:20)
仅当枚举未定义任何成员时才允许对枚举进行子类化。
允许对定义成员的枚举进行子类化会导致违反某些类型和实例的重要不变量。
https://docs.python.org/3/library/enum.html#restricted-subclassing-of-enumerations
所以没有,这不是直接可能的。
答案 1 :(得分:12)
直接调用Enum类并使用链允许扩展(连接)现有枚举。
我遇到了在使用CANopen时扩展枚举的问题 实现。参数索引的范围是0x1000到0x2000 对所有CANopen节点都是通用的,例如,范围从0x6000 向前取决于节点是否是驱动器,io-module等。
nodes.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)])
drives.py:
select CASE
WHEN Type = 'Reserved' THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS isReserved
from tablename
where CASE
WHEN Type = 'Reserved' THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END=CAST(0 AS bit)
答案 2 :(得分:8)
虽然不常见,但从许多模块创建枚举有时很有用。 aenum
1 库通过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
1 披露:我是Python stdlib Enum
,enum34
backport和Advanced Enumeration (aenum
)图书馆的作者。
答案 3 :(得分:6)
我在3.8上进行了测试。我们可以继承现有的枚举,但我们也需要从基类(在最后一个位置)进行。
新的Enum类必须具有一个基本Enum类,最多一个具体 数据类型以及所需的基于对象的混合类。命令 这些基类是:
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
class Cats(Enum):
SIBERIAN = "siberian"
SPHINX = "sphinx"
class Animals(Cats, Enum):
LABRADOR = "labrador"
CORGI = "corgi"
之后,您可以访问“动物中的猫”:
>>> Animals.SIBERIAN
<Cats.SIBERIAN: 'siberian'>
但是,如果您要遍历此枚举,则只能访问新成员:
>>> list(Animals)
[<Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]
实际上,这种方法是用于从基类继承方法,但是您可以将其用于具有这些限制的成员。
如上所述,编写一些函数以将两个枚举合并为一个。我已经写了这个例子:
def extend_enum(inherited_enum):
def wrapper(added_enum):
joined = {}
for item in inherited_enum:
joined[item.name] = item.value
for item in added_enum:
joined[item.name] = item.value
return Enum(added_enum.__name__, joined)
return wrapper
class Cats(Enum):
SIBERIAN = "siberian"
SPHINX = "sphinx"
@extend_enum(Cats)
class Animals(Enum):
LABRADOR = "labrador"
CORGI = "corgi"
但是在这里我们遇到了另一个问题。如果我们要比较成员,它将失败:
>>> Animals.SIBERIAN == Cats.SIBERIAN
False
在这里,我们可能只比较新创建的成员的名称和值:
>>> Animals.SIBERIAN.value == Cats.SIBERIAN.value
True
但是,如果我们需要在新的Enum上进行迭代,则可以正常运行:
>>> list(Animals)
[<Animals.SIBERIAN: 'siberian'>, <Animals.SPHINX: 'sphinx'>, <Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]
因此,请选择您的方式:简单继承,使用decorator进行继承仿真(实际上是重新创建),或添加新的依赖项(如aenum)(我尚未对其进行测试,但我希望它支持我描述的所有功能)。
答案 4 :(得分:1)
我选择使用元类方法来解决此问题。
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.
该元类的用法如下:
>>> 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>
请注意在命名空间冲突的情况下会发生什么:
>>> 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'
>>>
这是由于基enum.EnumMeta.__prepare__
返回一个特殊的enum._EnumDict
而不是典型的dict
对象,该对象在键分配上的行为不同。您可能希望通过用try
-except TypeError
将其包围来抑制此错误消息,或者可能有一种在调用super().__prepare__(...)
之前修改命名空间的方法。
此方法使用与源Enum
相同的名称/值对来创建新的Enum
,但是生成的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
>>>
答案 5 :(得分:0)
我认为您可以通过以下方式做到这一点:
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])
关键点是:
答案 6 :(得分:0)
另一种方式:
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}))
或:
LetterDict = {"A": 0, "B": 1}
Letter = Enum(value="Letter", names=LetterDict)
LetterExtendedDict = dict({"C": 2, "D": 3}, **LetterDict)
LetterExtended = Enum(value="Letter", names=LetterExtendedDict)
输出:
>>> 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>
答案 7 :(得分:0)
对于正确的类型说明,可以使用Union
运算符:
from enum import Enum
from typing import Union
class EventStatus(Enum):
success = 0
failure = 1
class BookingSpecificStatus(Enum):
duplicate = 2
unknown = 3
BookingStatus = Union[EventStatus, BookingSpecificStatus]
example_status: BookingStatus
example_status = BookingSpecificStatus.duplicate
example_status = EventStatus.success
答案 8 :(得分:0)
是的,您可以修改 Enum
。下面的示例代码有点笨拙,它显然依赖于 Enum
的内部结构,它没有任何业务可依赖。另一方面,它有效。
class ExtIntEnum(IntEnum):
@classmethod
def _add(cls, value, name):
obj = int.__new__(cls, value)
obj._value_ = value
obj._name_ = name
obj.__objclass__ = cls
cls._member_map_[name] = obj
cls._value2member_map_[value] = obj
cls._member_names_.append(name)
class Fubar(ExtIntEnum):
foo = 1
bar = 2
Fubar._add(3,"baz")
Fubar._add(4,"quux")
特别注意 obj = int.__new__()
行。 enum
模块跳过了几个环节,为应该枚举的类找到正确的 __new__
方法。我们在这里忽略这些圈是因为我们已经知道整数(或者更确切地说,int
的子类的实例)是如何创建的。
最好不要在生产代码中使用它。如果必须,您确实应该添加防止重复值或名称的保护措施。
答案 9 :(得分:0)
我想从 Django 的 IntegerChoices
继承,由于“无法扩展枚举”的限制,这是不可能的。我认为它可以通过一个相对简单的元类来完成。
CustomMetaEnum.py
:
class CustomMetaEnum(type):
def __new__(self, name, bases, namespace):
# Create empty dict to hold constants (ex. A = 1)
fields = {}
# Copy constants from the namespace to the fields dict.
fields = {key:value for key, value in namespace.items() if isinstance(value, int)}
# In case we're about to create a subclass, copy all constants from the base classes' _fields.
for base in bases:
fields.update(base._fields)
# Save constants as _fields in the new class' namespace.
namespace['_fields'] = fields
return super().__new__(self, name, bases, namespace)
# The choices property is often used in Django.
# If other methods such as values(), labels() etc. are needed
# they can be implemented below (for inspiration [Django IntegerChoice source][1])
@property
def choices(self):
return [(value,key) for key,value in self._fields.items()]
main.py
:
from CustomMetaEnum import CustomMetaEnum
class States(metaclass=CustomMetaEnum):
A = 1
B = 2
C = 3
print("States: ")
print(States.A)
print(States.B)
print(States.C)
print(States.choices)
print("MoreStates: ")
class MoreStates(States):
D = 22
pass
print(MoreStates.A)
print(MoreStates.B)
print(MoreStates.C)
print(MoreStates.D)
print(MoreStates.choices)
python3.8 main.py
:
States:
1
2
3
[(1, 'A'), (2, 'B'), (3, 'C')]
MoreStates:
1
2
3
22
[(22, 'D'), (1, 'A'), (2, 'B'), (3, 'C')]
答案 10 :(得分:0)
你不能扩展枚举,但你可以通过合并它们来创建一个新的。
Tested for Python 3.6
from enum import Enum
class DummyEnum(Enum):
a = 1
class AnotherDummyEnum(Enum):
b = 2
def merge_enums(class_name: str, enum1, enum2, result_type=Enum):
if not (issubclass(enum1, Enum) and issubclass(enum2, Enum)):
raise TypeError(
f'{enum1} and {enum2} must be derived from Enum class'
)
attrs = {attr.name: attr.value for attr in set(chain(enum1, enum2))}
return result_type(class_name, attrs, module=__name__)
result_enum = merge_enums(
class_name='DummyResultEnum',
enum1=DummyEnum,
enum2=AnotherDummyEnum,
)