在Python中将字符串转换为枚举类型

342

如何正确地将字符串转换为相应的枚举子类实例?似乎使用getattr(YourEnumType, str)可以完成任务,但我不确定它是否足够安全。

例如,假设我有一个枚举类型如下:

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

如何从字符串'debug'获得BuildType.debug的结果?


与原始问题相反,这不是一个“序列化”任务。 - Karl Knechtel
11个回答

558
这个功能已经内置在Enum中了。
>>> 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()]

14
如果需要对输入进行清洗,是否考虑设置回退值?比如类似于 Build.get('illegal', Build.debug) 这样的形式? - Hetzroni
1
@Hetzroni: Enum 没有 .get() 方法,但是你可以根据需要添加一个或者只需创建一个基础的 Enum 类并继承它。 - Ethan Furman
3
根据“先行为,后征得许可”的原则,您可以始终将访问包装在try / except KeyError子句中以返回默认值(正如Ethan所提到的那样,还可以将其包装在自己的函数/方法中)。 - Laogeodritt
12
@Dragonborn,调用Build('debug')不起作用。在这个例子中,类构造函数必须采用,即 200400。如果要传递名称,则必须使用方括号,就像答案已经指出的那样。 - Arthur Tacca
1
@Hetzroni,自Python 3.6起,Enum有一个特殊的类方法_missing_ - rysson
显示剩余7条评论

45

另一种选择(特别适用于您的字符串与枚举实例不是1对1映射的情况)是向您的Enum添加一个staticmethod,例如:

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')


2
有没有办法覆盖__getitem__或其他内置方法? - ClementWalter
我认为编译器或至少是Pycharm认为from_str函数返回一个str类型。使用另一个答案中描述的QuestionType [“some_string”],让Pycharm认为它是QuestionType类型。 - user1689987
@ClementWalter 是的,但由于它需要在枚举类型本身上实现,而不是在实例上实现,所以你需要在元类中完成。问题是 - enum.Enum 已经为你做了这个。你可以子类化enum.Enum的元类,也就是enum.EnumType,并重写它的__getitem__方法 - 但你真的需要它执行与默认值不同的操作吗? - Karl Knechtel

17
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 字符串转换为这样的枚举:class BuildType(Enum): debug = 200 release = 400 - Vladius
太棒了!使用__dict__getattr是一样的吗?我担心会与Python内部属性发生名称冲突.... - Vladius
哦...是的,它和getattr一样。我看不出有名称冲突的理由。你只是不能将关键字设置为类的字段。 - ADR

14

这是我对问题的类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`

1
今天,执行 SignInMethod('EMAIL') 方法将具有相同的效果 - Rafael Gomes Francisco

3
class LogLevel(IntEnum):
    critical = logging.CRITICAL
    fatal = logging.FATAL
    error = logging.ERROR
    warning = logging.WARNING
    info = logging.INFO
    debug = logging.DEBUG
    notset = logging.NOTSET

    def __str__(self):
        return f'{self.__class__.__name__}.{self.name}'

    @classmethod
    def _missing_(cls, value):
        if type(value) is str:
            value = value.lower()
            if value in dir(cls):
                return cls[value]

        raise ValueError("%r is not a valid %s" % (value, cls.__name__))

例子:

print(LogLevel('Info'))
print(LogLevel(logging.WARNING))
print(LogLevel(10))    # logging.DEBUG
print(LogLevel.fatal)
print(LogLevel(550))

输出:

LogLevel.info
LogLevel.warning
LogLevel.debug
LogLevel.critical
ValueError: 550 is not a valid LogLevel

2
哇,一个罕见的_sunder_。这个答案的好处是“插入即可”,因此可以与现有的“结构化”库一起使用,比如在我这里是cattrs,而无需进行任何定制。谢谢。 - toppk

3
在Python 3.11中,你也可以使用StrEnum。当然,你也需要将值替换为字符串,因为它是根据值而不是名称进行键入的(对于值使用enum.auto()是一个不错的解决方案)。
import enum

class BuildType(enum.StrEnum):
    debug = "200"
    release = "400"

print(BuildType("debug"))

非常重要的是要注意,StrEnum.getitem()的工作方式与Enum.getitem()不同,因此对于使用BuildType["debug"]的StrEnum,将始终导致KeyError,并且您必须使用您在这里展示的BuildType("debug")语法。这恰好与将字符串分配为值的常规Enum完全相反。 - undefined

2

将你的类签名更改为以下内容:

class BuildType(str, Enum):

8
可以加上更多的细节吗?那么该如何使用这个类呢? - Cleb
5
作者未进一步澄清。当你以这种方式声明你的类时,你可以使用枚举字符串值来创建枚举值的直接实例。例如,你可以设置A = "FIRST_VALUE",然后执行BuildType("FIRST_VALUE")将自动获取BuildType.A。但只有在字符串值与枚举名称相同时才适用于你的用例。 - Eric Castro

2

@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__或其他内置方法? - ClementWalter
1
以哪种方式进行改进? - Cleb
如果你在函数内部使用了类,最好使用@classmethod而不是@staticmethod。 - Jackiexiao
和 @Cleb 一样的问题。我猜这里的想法是,如果你要扩展它,这会使它更加灵活? - Neil Traft

1

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

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')

1

通过名称获取枚举实例

如果您想通过名称访问枚举成员,请使用项访问:

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

它对于意外的名称值引发 KeyError

通过值获取枚举实例

对于下面的 Enum 类,其中的值是字符串:

class BuildType(Enum):
    debug = 'debug'
    release = 'release'

枚举使用调用语法按值返回成员。
>>> BuildType('debug')
<BuildType.debug: 'debug'>

它对于意外的值引发了 ValueError。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接