【问题标题】:String-based enum in PythonPython中基于字符串的枚举
【发布时间】:2020-02-24 17:57:29
【问题描述】:

要封装我正在使用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)?

【问题讨论】:

  • MyEnum.state1.value?
  • 对不起,这对我来说和str(state)一样丑...
  • Testround.status 是什么类型?你能把它改成MyEnum吗?
  • 您的示例代码'state1' == state 是错误的——比较返回False
  • @EthanFurman:它没有抛出,但确实结果远非令人满意。谢谢!更正。

标签: python python-3.x string enums


【解决方案1】:

看来str类同时继承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)

附带说明一下,formatted strings 似乎不需要特殊的继承技巧,它甚至仅适用于 Enum 继承:

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

【讨论】:

【解决方案2】:

虽然strEnum 之间的mixin 类可以解决这个问题,但您也应该始终考虑为工作找到合适的工具

有时,正确的工具很容易就是带有字符串值的 MODULE_CONSTANT。例如,logging 有一些常量,如 DEBUG、INFO 等,具有有意义的值——即使在这种情况下它们是 ints。

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

【讨论】:

  • Enum 已创建,因此不需要不透明的模块常量。
  • @EthanFurman 那为什么 logging.DEBUG 和朋友没有被弃用呢?
  • 它们不会被弃用——它们将被相应的IntEnum 替换。保持标准库尽可能稳定是标准策略,这意味着不要为了利用每个新功能而大规模重写它。到目前为止,httpsocketre 模块常量已被替换(可能还有几个我暂时不记得的)。
  • PEP 435的相关部分。
  • 哇,感觉好像很久没有对话了!那里的要点与我之前的评论有关:没有充分理由不重写标准库。魔术字符串的情况更难制作,因为字符串通常是不言自明的。最有可能进行Enum 转换的模块是面向用户的整数常量(例如rehttp)。此外,不转换 stdlib 的特定部分的一个重要原因是它是否在 Enum 可以导入之前使用。这些都是不影响标准库之外代码的原因。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    只需使用 .value :

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

    【讨论】:

    • 该问题明确指出调用.value 是不可接受的解决方案。
    【解决方案5】:

    如果你想直接使用字符串,你可以考虑使用

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

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

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

    【讨论】:

      【解决方案6】:

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

      from enum import StrEnum
      
      class Directions(StrEnum):
          NORTH = 'north',    # notice the trailing comma
          SOUTH = 'south'
      
      print(Directions.NORTH)
      >>> north
      

      请参考docsdesign discussion进一步了解。

      如果你运行的是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
      

      您可以阅读更多关于它的信息here


      此外,文档中也提到了这一点 - 如何根据其他类创建自己的枚举:

      虽然 IntEnum 是 enum 模块的一部分,但很简单 独立实施:

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

      一些规则:

      当继承 Enum 时,混合类型必须出现在 Enum 本身之前 碱基序列,如上面的 IntEnum 示例。

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

      当混入另一种数据类型时,value属性不是 与枚举成员本身相同,尽管它是等价的并且将 比较相等。

      % 样式格式:%s 和 %r 调用 Enum 类的 str() 和 repr() 分别;其他代码(例如 IntEnum 的 %i 或 %h)将枚举成员视为其混合类型。

      格式化字符串字面量,str.format() 和 format() 将使用 混合类型的 format() 除非 str() 或 format() 是 在子类中被覆盖,在这种情况下,被覆盖的方法或 将使用枚举方法。使用 !s 和 !r 格式代码强制 使用 Enum 类的 str() 和 repr() 方法。

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

      【讨论】:

      • StrEnum 的 3.11
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-06
      • 2022-06-11
      相关资源
      最近更新 更多