如何在Python中使用字符串值创建可索引和可切片的枚举?

rqcrx0a6  于 8个月前  发布在  Python
关注(0)|答案(4)|浏览(87)

我有一个这样的文件:

class Level(Enum):
    prerequisite_level: Optional["Level"]
    dependent_level: Optional["Level"]
    lower_priority_levels: List["Level"]
    greater_priority_levels: List["Level"]

    DATA_CHECK = "data check"
    DESIGN_CHECK = "design check"
    ALERT = "alert"

字符串
枚举值是以特定的顺序排列的,基于每个级别,我需要能够获得前一个,下一个,以及所有的前一个和下一个。我相信我需要能够用数字索引级别来获得这些值,所以我添加了一个常量来做到这一点:

INCREASING_PRIORITY_LEVELS: List[Level] = list(Level)

for priority_level_index, threshold_level in enumerate(Level):
    if priority_level_index > 0:
        threshold_level.prerequisite_level = Level[priority_level_index - 1]
    else:
        threshold_level.prerequisite_level = None

    if priority_level_index < len(Level) - 1:
        threshold_level.dependent_level = Level[priority_level_index + 1]
    else:
        threshold_level.dependent_level = None

    threshold_level.lower_priority_levels = Level[:priority_level_index]
    threshold_level.greater_priority_levels = Level[priority_level_index + 1:]


这是一个笨拙的方法,我想去掉这个常量。我需要实现__getitem__或其他东西来实现它吗?

lokaqttq

lokaqttq1#

你可以子类化EnumMeta来覆盖__getitem__方法,并附加条件以返回一个Enum值的列表或一个基于给定索引的特定Enum值,并创建一个Enum的子类,以前面提到的EnumMeta子类作为元类,这样Enum这个新子类的任何子类都可以根据需要进行索引:

from itertools import islice
from enum import Enum, EnumMeta

class IndexableEnumMeta(EnumMeta):
    def __getitem__(cls, index):
        if isinstance(index, slice):
            return [cls._member_map_[i] for i in islice(cls._member_map_, index.start, index.stop, index.step)]
        if isinstance(index, int):
            return cls._member_map_[next(islice(cls._member_map_, index, index + 1))]
        return cls._member_map_[index]

class IndexableEnum(Enum, metaclass=IndexableEnumMeta):
    pass

class Level(IndexableEnum):
    DATA_CHECK = "data check"
    DESIGN_CHECK = "design check"
    ALERT = "alert"

字符串
Level[1:3]返回:

[<Level.DESIGN_CHECK: 'design check'>, <Level.ALERT: 'alert'>]


Level[1]返回:

Level.DESIGN_CHECK


(感谢@EthanFurman指出了子类化EnumMeta的可行性。

x33g5p2x

x33g5p2x2#

class Level(Enum):

    prerequisite_level: Optional["Level"]
    dependent_level: Optional["Level"]
    lower_priority_levels: List["Level"]
    greater_priority_levels: List["Level"]

    DATA_CHECK = "data check"
    DESIGN_CHECK = "design check"
    ALERT = "alert"

字符串
我很难理解上面的内容:...[注解澄清了前四个应该是属性,prequisitedependent分别是前一个和后一个成员]。
解决方案是在初始化当前成员时修改以前的成员(技巧是在成员创建和初始化之后才将当前成员添加到父Enum)。下面是使用stdlib的Enum 1(Python 3.6及更高版本)的解决方案:

from enum import Enum, auto

class Level(str, Enum):
    #
    def __init__(self, name):
        # create priority level lists
        self.lower_priority_levels = list(self.__class__._member_map_.values())
        self.greater_priority_levels = []
        # update previous members' greater priority list
        for member in self.lower_priority_levels:
            member.greater_priority_levels.append(self)
        # and link prereq and dependent
        self.prerequisite = None
        self.dependent = None
        if self.lower_priority_levels:
            self.prerequisite = self.lower_priority_levels[-1]
            self.prerequisite.dependent = self
    #
    def _generate_next_value_(name, start, count, last_values, *args, **kwds):
        return (name.lower().replace('_',' '), ) + args
    #
    DATA_CHECK = auto()
    DESIGN_CHECK = auto()
    ALERT = auto()


用途:

>>> list(Level)
[<Level.DATA_CHECK: 'data check'>, <Level.DESIGN_CHECK: 'design check'>, <Level.ALERT: 'alert'>]

>>> Level.DATA_CHECK.prerequisite
None

>>> Level.DATA_CHECK.dependent
<Level.DESIGN_CHECK: 'design check'>

>>> Level.DESIGN_CHECK.prerequisite
<Level.DATA_CHECK: 'data check'>

>>> Level.DESIGN_CHECK.dependent
<Level.ALERT: 'alert'>

>>> Level.ALERT.prerequisite
<Level.DESIGN_CHECK: 'design check'>

>>> Level.ALERT.dependent
None


注意:如果你不想看到两次名字,自定义的__repr__可以只显示枚举和成员名:

def __repr__(self):
        return '<%s.%s>' % (self.__class__.__name__, self.name)


然后你会看到:

>>> Level.DESIGN_CHECK
<Level.DESIGN_CHECK>


1如果使用Python 3.5或更早版本,则需要使用aenum 2。
2披露:我是Python stdlib Enumenum34 backportAdvanced Enumeration ( aenum )库的作者。

kiayqfof

kiayqfof3#

另一个版本建立在@blhsing的答案上,带有类型和对查找成员索引的支持。不幸的是,Mypy不完全支持元类,所以类型变量过于通用。

import enum
import itertools
from typing import TypeVar

_EnumMemberT = TypeVar("_EnumMemberT")

class _IndexableEnumType(enum.EnumType):
    def __getitem__(self: type[_EnumMemberT], name: str | int) -> _EnumMemberT:
        if isinstance(name, int):
            try:
                return next(itertools.islice(enum.EnumType.__iter__(self), name, None))
            except StopIteration:
                raise IndexError("enum index out of range") from None
        return enum.EnumType.__getitem__(self, name)

    def index(self: type[_EnumMemberT], name: str) -> int:
        for index, member in enumerate(enum.EnumType.__iter__(self)):
            if member.name == name:  # type: ignore[attr-defined]
                return index
        raise ValueError(f"'{name}' is not in enum")

class IndexableEnum(enum.Enum, metaclass=_IndexableEnumType):
    pass

字符串
然后可以像往常一样声明IndexableEnum,并像列表一样使用索引:

>>> class MyEnum(IndexableEnum):
...     Foo = "bar"
...     Baz = "qux"
... 
>>> 
>>> MyEnum[0] 
<MyEnum.Foo: 'bar'>
>>>                    
>>> MyEnum.index('Foo')
0
>>>

5tmbdcev

5tmbdcev4#

在使用方面实现相同结果的可能替代方案是使用collections.namedtuple

from collections import namedtuple
LevelSequence = namedtuple('Level', ('DATA_CHECK', 'DESIGN_CHECK', 'ALERT'))
Level = LevelSequence('data check', 'design check', 'alert')

字符串
因此:

  • Level.DESIGN_CHECKLevel[1]都返回'design check',并且
  • Level[1:3]返回('design check', 'alert')

相关问题