fal*_*cin 42 python enums python-3.x
什么是Enum
在Python 3.4中扩展类型的最佳实践,是否有可能这样做?
例如:
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
Run Code Online (Sandbox Code Playgroud)
目前没有可能的方法来创建一个带有成员的基本枚举类,并在其他枚举类中使用它(如上例所示).有没有其他方法来实现Python枚举的继承?
Gin*_*lus 24
仅当枚举未定义任何成员时,才允许对枚举进行子类化.
允许对定义成员的枚举进行子类化会导致违反某些类型和实例的重要不变量.
https://docs.python.org/3/library/enum.html#restricted-subclassing-of-enumerations
所以不,这不是直接可能的.
Jul*_*l3k 14
直接调用Enum类并使用链允许扩展(连接)现有枚举.
我在处理CANopen实现时遇到了扩展枚举的问题.范围从0x1000到0x2000的参数索引对于所有CANopen节点是通用的,而例如从0x6000开始的范围取决于节点是否是驱动器,io模块等.
nodes.py:
from enum import IntEnum
class IndexGeneric(IntEnum):
""" This enum holds the index value of genric object entrys
"""
DeviceType = 0x1000
ErrorRegister = 0x1001
Idx = IndexGeneric
Run Code Online (Sandbox Code Playgroud)
drives.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)])
Run Code Online (Sandbox Code Playgroud)
Eth*_*man 12
虽然不常见,但从许多模块创建枚举有时很有用.在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
Run Code Online (Sandbox Code Playgroud)
1披露:我是Python stdlibEnum
,enum34
backport和Advanced Enumeration(aenum
) 库的作者.
Pau*_*l P 12
这里已经有很多好的答案,但这里有另一个纯粹使用Enum 的函数式 API 的答案。
可能不是最漂亮的解决方案,但它避免了代码重复,开箱即用,不需要额外的包/库,并且它应该足以覆盖大多数用例:
from enum import Enum
class EventStatus(Enum):
success = 0
failure = 1
BookingStatus = Enum(
"BookingStatus",
[es.name for es in EventStatus] + ["duplicate", "unknown"],
start=0,
)
for bs in BookingStatus:
print(bs.name, bs.value)
# success 0
# failure 1
# duplicate 2
# unknown 3
Run Code Online (Sandbox Code Playgroud)
如果您想明确指定的值,可以使用:
BookingStatus = Enum(
"BookingStatus",
[(es.name, es.value) for es in EventStatus] + [("duplicate", 6), ("unknown", 7)],
)
for bs in BookingStatus:
print(bs.name, bs.value)
# success 0
# failure 1
# duplicate 6
# unknown 7
Run Code Online (Sandbox Code Playgroud)
小智 10
我在 3.8 上以这种方式进行了测试。我们可以继承现有的枚举,但我们也需要从基类(最后一个位置)继承。
一个新的 Enum 类必须有一个基本的 Enum 类,最多一种具体的数据类型,以及根据需要尽可能多的基于对象的 mixin 类。这些基类的顺序是:
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
Run Code Online (Sandbox Code Playgroud)
class Cats(Enum):
SIBERIAN = "siberian"
SPHINX = "sphinx"
class Animals(Cats, Enum):
LABRADOR = "labrador"
CORGI = "corgi"
Run Code Online (Sandbox Code Playgroud)
之后,您可以访问来自动物的猫:
>>> Animals.SIBERIAN
<Cats.SIBERIAN: 'siberian'>
Run Code Online (Sandbox Code Playgroud)
但是,如果您想遍历此枚举,则只能访问新成员:
>>> list(Animals)
[<Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]
Run Code Online (Sandbox Code Playgroud)
实际上这种方式是用于从基类继承方法,但您可以将它用于具有这些限制的成员。
如上所述,编写一些函数将两个枚举合二为一。我写了那个例子:
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"
Run Code Online (Sandbox Code Playgroud)
但在这里我们遇到了另一个问题。如果我们想比较成员,它会失败:
>>> Animals.SIBERIAN == Cats.SIBERIAN
False
Run Code Online (Sandbox Code Playgroud)
这里我们可能只比较新创建的成员的名称和值:
>>> Animals.SIBERIAN.value == Cats.SIBERIAN.value
True
Run Code Online (Sandbox Code Playgroud)
但是如果我们需要对新的 Enum 进行迭代,它可以正常工作:
>>> list(Animals)
[<Animals.SIBERIAN: 'siberian'>, <Animals.SPHINX: 'sphinx'>, <Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]
Run Code Online (Sandbox Code Playgroud)
所以选择你的方式:简单继承,用装饰器模拟继承(实际上是娱乐),或者添加一个像 aenum 这样的新依赖项(我没有测试过,但我希望它支持我描述的所有功能)。
我选择使用元类方法来解决这个问题。
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.
Run Code Online (Sandbox Code Playgroud)
这个元类可以这样使用:
>>> 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>
Run Code Online (Sandbox Code Playgroud)
这种方法Enum
使用与源Enum
s相同的名称-值对创建一个新的,但结果Enum
成员仍然是唯一的。名称和值将是相同的,但是按照 Python 的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
>>>
Run Code Online (Sandbox Code Playgroud)
注意命名空间冲突时会发生什么:
>>> 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'
>>>
Run Code Online (Sandbox Code Playgroud)
这是因为基enum.EnumMeta.__prepare__
返回一个特殊的enum._EnumDict
而不是dict
在键分配时表现不同的典型对象。您可能希望通过用try
-包围它来抑制此错误消息except TypeError
,或者可能有一种方法可以在调用之前修改命名空间super().__prepare__(...)
。
小智 5
对于正确的类型规范,您可以使用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
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
14943 次 |
最近记录: |