Python 中基于字符串的枚举

为了封装状态列表,我使用 enum模块:

from enum import Enum


class MyEnum(Enum):
state1='state1'
state2 = 'state2'


state = MyEnum.state1
MyEnum['state1'] == state  # here it works
'state1' == state  # here it does not throw but returns False (fail!)

然而,问题在于我需要在脚本的许多上下文中无缝地使用这些值作为字符串,比如:

select_query1 = select(...).where(Process.status == str(MyEnum.state1))  # works but ugly


select_query2 = select(...).where(Process.status == MyEnum.state1)  # throws exeption

如何避免调用额外的类型转换(上面的 str(state))或基础值(state.value) ?

68019 次浏览

似乎只要继承 str类和 Enum类就足够了:

from enum import Enum


class MyEnum(str, Enum):
state1 = 'state1'
state2 = 'state2'

棘手的是继承链 很重要中类的 命令是这样的:

class MyEnum(Enum, str):
state1 = 'state1'
state2 = 'state2'

投掷:

TypeError: new enumerations should be created as `EnumName([mixin_type, ...] [data_type,] enum_type)`

使用正确的类,MyEnum上的以下操作可以正常进行:

print('This is the state value: ' + state)

值得注意的是,对于 格式化字符串来说,似乎不需要特殊的继承技巧,因为它甚至只对 Enum继承有效:

msg = f'This is the state value: {state}'  # works without inheriting from str

虽然 strEnum之间的 Mixin 类可以解决这个问题,但是您应该始终考虑获得 正确的工具

有时候,正确的工具很容易只是一个带字符串值的 MODULE _ CONSTANT。例如,logging有一些常量,比如 DEBUG、 INFO 等,它们具有有意义的值——即使在本例中它们是 int

枚举是一个很好的工具,我经常使用它们。但是,主要是将它们与同一枚举的其他成员进行比较,这就是为什么要将它们与字符串进行比较,例如,字符串需要跳过另外一个循环。

如果关联的字符串值是有效的 Python 名称,那么您可以使用如下 .name属性获取 enum 成员的名称:

from enum import Enum
class MyEnum(Enum):
state1=0
state2=1


print (MyEnum.state1.name)  # 'state1'


a = MyEnum.state1
print(a.name)  # 'state1'

如果关联的字符串值是任意字符串,那么可以这样做:

class ModelNames(str, Enum):
gpt2 = 'gpt2'
distilgpt2 = 'distilgpt2'
gpt2_xl = 'gpt2-XL'
gpt2_large = 'gpt2-large'


print(ModelNames.gpt2) # 'ModelNames.gpt2'
print(ModelNames.gpt2 is str) # False
print(ModelNames.gpt2_xl.name) # 'gpt2_xl'
print(ModelNames.gpt2_xl.value) # 'gpt2-XL'

试试这个在线: https://repl.it/@sytelus/enumstrtest

价值:

MyEnum.state1.value == 'state1'
# True

如果希望直接使用字符串,可以考虑使用

MyEnum = collections.namedtuple(
"MyEnum", ["state1", "state2"]
)(
state1="state1",
state2="state2"
)

而不是枚举。对此进行迭代或执行 MyEnum.state1将直接给出字符串值。在同一语句中创建 namedtuple 意味着只能有一个。

显然,不使用 Enum 会有一些折衷,所以这取决于您更看重什么。

通过阅读文档(例如,我没有尝试,因为我使用的是旧版本的 Python,但我相信文档) ,因为 Python 3.11可以执行以下操作:

from enum import StrEnum


class Directions(StrEnum):
NORTH = 'north',    # notice the trailing comma
SOUTH = 'south'


print(Directions.NORTH)
>>> north

请参阅 医生设计讨论进一步了解。

如果您正在运行 Python 3.6 + ,执行 pip install StrEnum,然后您可以执行以下操作(经我确认) :

from strenum import StrEnum


class URLs(StrEnum):
GOOGLE = 'www.google.com'
STACKOVERFLOW = 'www.stackoverflow.com'


print(URLs.STACKOVERFLOW)


>>> www.stackoverflow.com

你可以阅读更多关于它的资料。


另外,文档中也提到了这一点——如何基于其他类创建自己的枚举:

虽然 IntEnum 是 enum 模块的一部分,但是 独立执行:

类 IntEnum (int,Enum) : Pass 这演示了如何定义类似的派生枚举; 例如,混合了 str 而不是 int 的 StrEnum。

一些规则:

子类化 Enum 时,mix-in 类型必须出现在 碱基的序列,如上面的 IntEnum 示例所示。

虽然 Enum 可以有任何类型的成员,但是一旦混合了额外的 类型,则所有成员必须具有该类型的值,例如上面的 int。 此限制不适用于只添加方法和 不要指定其他类型。

当混合使用其他数据类型时,value 属性不是 与枚举成员本身相同,尽管它是等价的并且将 比较平等。

%-style 格式设置:% s 和% r 调用 Enum 类的 STR()和 Repr () ; 其他代码(如 IntEnum 的% i 或% h)将 enum 成员视为其混合类型。

格式化的字符串文本、 str.format ()和 format ()将使用 混合类型的 格式() ,除非 STR()或 格式()是 在子类中重写,在这种情况下,重写的方法或 将使用枚举方法。请使用! s 和! r 格式代码强制执行 使用 Enum 类的 STR()和 公司代表()方法。

资料来源: https://docs.python.org/3/library/enum.html#others

使用这个值有什么错?

Imho,除非在 StrEnum 中使用 Python 3.11版本,否则我只需在适当的 Enum 类中覆盖 __str__(self)方法:

class MyStrEnum(str, Enum):


OK     = 'OK'
FAILED = 'FAILED'


def __str__(self) -> str:
return self.value

最好的

auto:

from enum import Enum, auto


class AutoStrEnum(str, Enum):
"""
StrEnum where auto() returns the field name.
See https://docs.python.org/3.9/library/enum.html#using-automatic-values
"""
@staticmethod
def _generate_next_value_(name: str, start: int, count: int, last_values: list) -> str:
return name


class MyEnum(AutoStrEnum):
STATE_1 = auto()
STATE_2 = auto()

试试看:

MyEnum.STATE_1 == "STATE_1"  # True