Перетворити рядок в Enum в Python


141

Цікаво, який правильний спосіб перетворення (десеріалізації) рядка до класу Enum Python's Enum. Здається, getattr(YourEnumType, str)це виконує роботу, але я не впевнений, чи достатньо це безпечно.

Щоб бути більш конкретним, я хотів би перетворити 'debug'рядок в такий об'єкт Enum:

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

Відповіді:


213

Ця функціональність вже вбудована в 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)?
Хетцроні

1
@Hetzroni: Enumне поставляється з .get()методом, але ви можете додати його за потребою або просто зробити базовий Enumклас і завжди успадковувати його.
Етан Фурман

@Hetzroni: Відповідно до принципу "просити пробачення, а не дозволу", ти завжди можеш обернути доступ в пункт "спробувати", за винятком клавіші 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')


1
Дуже пов’язано, якщо ви часто робите це: pydantic-docs.helpmanual.io
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

Я маю на увазі, що я хотів би перетворити 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',))

EDIT: Це виявляється помилковим. Подяка коментатору за вказівку на мою помилку


Використовуючи Python 3.6.6, я не міг відтворити цю поведінку. Я думаю, ви, можливо, помилилися під час тестування (я знаю, що я це зробив вперше, перевіряючи це). Якщо ви випадково поставили ,(кома) після кожного елемента (як якщо б елементи були списком), він розглядає кожен елемент як кортеж. (тобто a = 'aaa',насправді те саме, що a = ('aaa',))
Multihunter

Ви маєте рацію, в моєму коді була різна помилка. Я якось подумав, що вам потрібно відкласти ,за кожним рядком, визначаючи перерахунок, який перетворив значення в кортежі
Sstuber
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.