【问题标题】:Convert string to Enum in Python在 Python 中将字符串转换为枚举
【发布时间】:2023-04-07 15:31:02
【问题描述】:

我想知道将字符串转换(反序列化)为 Python 的 Enum 类的正确方法是什么。似乎getattr(YourEnumType, str) 可以完成这项工作,但我不确定它是否足够安全。

更具体地说,我想将 'debug'string 转换为 Enum 对象,如下所示:

class BuildType(Enum):
    debug = 200
    release = 400

【问题讨论】:

    标签: python string serialization enums type-conversion


    【解决方案1】:

    我的类似 Java 的解决方案。希望它可以帮助某人...

    from enum import Enum, auto
    
    
    class SignInMethod(Enum):
        EMAIL = auto(),
        GOOGLE = auto()
    
        @classmethod
        def value_of(cls, value):
            for k, v in cls.__members__.items():
                if k == value:
                    return v
            else:
                raise ValueError(f"'{cls.__name__}' enum not found for '{value}'")
    
    
    sim = SignInMethod.value_of('EMAIL')
    assert sim == SignInMethod.EMAIL
    assert sim.name == 'EMAIL'
    assert isinstance(sim, SignInMethod)
    # SignInMethod.value_of("invalid sign-in method")  # should raise `ValueError`
    

    【讨论】:

    • 今天做SignInMethod('EMAIL')跟那个方法效果一样
    【解决方案2】:

    将您的班级签名更改为:

    class BuildType(str, Enum):
    

    【讨论】:

    • 您能补充更多细节吗?那么如何使用该类?
    【解决方案3】:

    此功能已内置于 Enum [1]:

    >>> from enum import Enum
    >>> class Build(Enum):
    ...   debug = 200
    ...   build = 400
    ... 
    >>> Build['debug']
    <Build.debug: 200>
    

    成员名称区分大小写,因此如果正在转换用户输入,您需要确保大小写匹配:

    an_enum = input('Which type of build?')
    build_type = Build[an_enum.lower()]
    

    [1] 官方文档:Enum programmatic access

    【讨论】:

    • 如果输入需要被清理,那么备用值呢? Build.get('illegal', Build.debug) 之类的东西?
    • @Hetzroni: Enum 没有 .get() 方法,但您可以根据需要添加一个,或者只创建一个基本的 Enum 类并始终从该类继承。
    • @Hetzroni:根据“请求宽恕,而不是许可”原则,您始终可以将访问权限包含在 try/except KeyError 子句中以返回默认值(正如 Ethan 所提到的,可以选择包装这个在你自己的函数/方法中)。
    • 这里值得注意 - 如果使用它进行序列化/反序列化,请为此序列化 name 属性,因此使用 Build.debug.name 而不是 str(Build.debug) 进行这种查找工作(否则它会尝试在不存在的反序列化端找到Build.debug)。
    • @Dragonborn 打电话给Build('debug') 是行不通的。类构造函数必须采用 ,即在本例中为 200400。如答案所述,要传递 name 您必须使用方括号。
    【解决方案4】:
    def custom_enum(typename, items_dict):
        class_definition = """
    from enum import Enum
    
    class {}(Enum):
        {}""".format(typename, '\n    '.join(['{} = {}'.format(k, v) for k, v in items_dict.items()]))
    
        namespace = dict(__name__='enum_%s' % typename)
        exec(class_definition, namespace)
        result = namespace[typename]
        result._source = class_definition
        return result
    
    MyEnum = custom_enum('MyEnum', {'a': 123, 'b': 321})
    print(MyEnum.a, MyEnum.b)
    

    或者您需要将字符串转换为已知枚举吗?

    class MyEnum(Enum):
        a = 'aaa'
        b = 123
    
    print(MyEnum('aaa'), MyEnum(123))
    

    或者:

    class BuildType(Enum):
        debug = 200
        release = 400
    
    print(BuildType.__dict__['debug'])
    
    print(eval('BuildType.debug'))
    print(type(eval('BuildType.debug')))    
    print(eval(BuildType.__name__ + '.debug'))  # for work with code refactoring
    

    【讨论】:

    • 我的意思是我想将debug 字符串转换为这样的枚举:python class BuildType(Enum): debug = 200 release = 400
    • 很棒的提示!使用__dict__getattr 一样吗?我担心与内部 Python 属性发生名称冲突......
    • 哦...是的,它与getattr 相同。我认为没有名称冲突的原因。您只是不能将关键字设置为类的字段。
    【解决方案5】:

    由于MyEnum['dontexist'] 将导致错误KeyError: 'dontexist',您可能希望静默失败(例如,返回无)。在这种情况下,您可以使用以下静态方法:

    class Statuses(enum.Enum):
        Unassigned = 1
        Assigned = 2
    
        @staticmethod
        def from_str(text):
            statuses = [status for status in dir(
                Statuses) if not status.startswith('_')]
            if text in statuses:
                return getattr(Statuses, text)
            return None
    
    
    Statuses.from_str('Unassigned')
    

    【讨论】:

      【解决方案6】:

      对@rogueleaderr 答案的改进:

      class QuestionType(enum.Enum):
          MULTI_SELECT = "multi"
          SINGLE_SELECT = "single"
      
          @classmethod
          def from_str(cls, label):
              if label in ('single', 'singleSelect'):
                  return cls.SINGLE_SELECT
              elif label in ('multi', 'multiSelect'):
                  return cls.MULTI_SELECT
              else:
                  raise NotImplementedError
      

      【讨论】:

      • 有什么方法可以覆盖__getitem__ 或其他一些内置方法吗?
      • 从哪方面提高?
      • 如果你在你的函数中使用Class,你最好使用@classmethod而不是@staticmethod
      【解决方案7】:

      我只想通知这在 python 3.6 中不起作用

      class MyEnum(Enum):
          a = 'aaa'
          b = 123
      
      print(MyEnum('aaa'), MyEnum(123))
      

      您必须将数据作为这样的元组提供

      MyEnum(('aaa',))
      

      编辑: 事实证明这是错误的。感谢评论者指出我的错误

      【讨论】:

      • 使用 Python 3.6.6,我无法重现此行为。我认为您在测试时可能犯了一个错误(我知道我是第一次检查时犯了这个错误)。如果您不小心在每个元素之后放置了一个,(逗号)(就好像这些元素是一个列表一样),那么它会将每个元素视为一个元组。 (即a = 'aaa',实际上与a = ('aaa',)相同)
      • 你是对的,这是我的代码中的不同错误。我不知何故认为你需要在每一行后面加上,,同时定义以某种方式将值转换为元组的枚举
      【解决方案8】:

      另一种选择(如果您的字符串未将 1-1 映射到您的枚举案例特别有用)是将 staticmethod 添加到您的 Enum,例如:

      class QuestionType(enum.Enum):
          MULTI_SELECT = "multi"
          SINGLE_SELECT = "single"
      
          @staticmethod
          def from_str(label):
              if label in ('single', 'singleSelect'):
                  return QuestionType.SINGLE_SELECT
              elif label in ('multi', 'multiSelect'):
                  return QuestionType.MULTI_SELECT
              else:
                  raise NotImplementedError
      

      那你就可以question_type = QuestionType.from_str('singleSelect')

      【讨论】:

      • 非常相关,如果您发现自己经常这样做:pydantic-docs.helpmanual.io
      • 有什么方法可以覆盖__getitem__或其他一些内置方法吗?
      猜你喜欢
      • 2010-10-03
      • 1970-01-01
      • 2010-09-06
      • 2019-03-10
      • 2014-03-18
      • 2015-06-10
      相关资源
      最近更新 更多