Преобразовать строку в Enum в Python

143

Интересно, как правильно преобразовать (десериализовать) строку в класс Python Enum. Похоже, getattr(YourEnumType, str)работает, но я не уверен, достаточно ли это безопасно.

Чтобы быть более конкретным, я хотел бы преобразовать 'debug'строку в объект Enum следующим образом:

class BuildType(Enum):
    debug = 200
    release = 400
Vladius
источник

Ответы:

215

Эта функциональность уже встроена в Enum [1]:

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

[1] Официальные документы: Enum programmatic access

Итан Фурман
источник
6
Как насчет резервного значения на случай, если необходимо очистить ввод? Что-то в этом роде Build.get('illegal', Build.debug)?
Hetzroni 05
1
@Hetzroni: Enumне имеет .get()метода, но вы можете добавить его по мере необходимости или просто создать базовый Enumкласс и всегда наследовать от него.
Итан Фурман
@Hetzroni: Согласно принципу «просить прощения, а не разрешения», вы всегда можете заключить доступ в предложение try / except KeyError, чтобы вернуть значение по умолчанию (и, как упомянул Итан, при желании оберните это в свою собственную функцию / метод) ,
Laogeodritt
1
Почетное упоминание Build('debug')
Dragonborn
2
@Dragonborn Звонить не получится Build('debug'). Конструктор класса должен принимать значение , т.е. 200или 400в этом примере. Чтобы передать имя, вы должны использовать квадратные скобки, как уже сказано в ответе.
Артур Такка,
17

Другая альтернатива (особенно полезная, если ваши строки не сопоставляют 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')

rogueleaderr
источник
1
Очень похоже
driftcatcher
6
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)

Или вам нужно преобразовать строку в известный Enum?

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
ADR
источник
Я имею в виду, что я хотел бы преобразовать debugстроку в такое перечисление: python class BuildType(Enum): debug = 200 release = 400
Владиус
Отличные советы! Используется так __dict__же, как getattr? Меня беспокоят конфликты имен с внутренними атрибутами Python ....
Владиус
Ой ... да он такой же как getattr. Я не вижу причин для коллизии имен. Вы просто не можете установить ключевое слово как поле класса.
ADR
4

Мое Java-подобное решение проблемы. Надеюсь, это кому-то поможет ...

    from enum import Enum, auto


    class SignInMethod(Enum):
        EMAIL = auto(),
        GOOGLE = auto()

        @staticmethod
        def value_of(value) -> Enum:
            for m, mm in SignInMethod.__members__.items():
                if m == value.upper():
                    return mm


    sim = SignInMethod.value_of('EMAIL')
    print("""TEST
    1). {0}
    2). {1}
    3). {2}
    """.format(sim, sim.name, isinstance(sim, SignInMethod)))
Митч
источник
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
Джавед
источник
-2

Я просто хочу уведомить, что это не работает в python 3.6

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

print(MyEnum('aaa'), MyEnum(123))

Вам нужно будет предоставить данные в виде кортежа, подобного этому

MyEnum(('aaa',))

РЕДАКТИРОВАТЬ: это оказывается ложным. Благодарим комментатора за указание на мою ошибку

Sstuber
источник
Используя Python 3.6.6, я не смог воспроизвести такое поведение. Я думаю, что вы, возможно, допустили ошибку во время тестирования (я знаю, что сделал первый раз, когда проверял это). Если вы случайно поставили ,(запятую) после каждого элемента (как если бы элементы были списком), тогда каждый элемент будет рассматриваться как кортеж. (т.е. a = 'aaa',фактически то же самое, что и a = ('aaa',))
Multihunter
Вы правы, в моем коде была другая ошибка. Я ,почему- то подумал, что вам нужно поместить за каждой строкой при определении перечисления, которое каким-то образом превращает значения в кортежи
Sstuber