Converter string em Enum em Python

142

Gostaria de saber qual é a maneira correta de converter (desserializar) uma string para uma classe Enum do Python. Parece que getattr(YourEnumType, str)faz o trabalho, mas não tenho certeza se é seguro o suficiente.

Apenas para ser mais específico, gostaria de converter uma 'debug'string em um objeto Enum como este:

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

Respostas:

214

Essa funcionalidade já está embutida no Enum [1]:

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

[1] Documentos oficiais: Enum programmatic access

Ethan Furman
fonte
6
Que tal um valor de fallback no caso de a entrada precisar ser higienizada? Algo no tipo de Build.get('illegal', Build.debug)?
Hetzroni
1
@Hetzroni: Enumnão vem com um .get()método, mas você pode adicionar um conforme necessário, ou apenas criar uma Enumclasse base e sempre herdar disso.
Ethan Furman
@Hetzroni: De acordo com o princípio "pedir perdão, não permitir permissão", você sempre pode envolver o acesso em uma cláusula try / except KeyError para retornar o padrão (e, como Ethan mencionou, opcionalmente, envolva-o em sua própria função / método) .
Laogeodritt
1
Menção honrosa Build('debug')
Dragonborn
2
@Dragonborn Não daria certo ligar Build('debug'). O construtor da classe deve receber o valor , 200ou seja, 400neste exemplo. Para passar o nome, você deve usar colchetes, como a resposta já diz.
Arthur Tacca 20/01
17

Outra alternativa (especialmente útil se as seqüências de caracteres não mapeiam 1-1 para os casos de enumeração) é adicionar a staticmethodao seu Enum, por exemplo:

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

Então você pode fazer question_type = QuestionType.from_str('singleSelect')

rogueleaderr
fonte
1
Muito relacionado, se você se encontra fazendo isso muitas vezes: 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)

Ou você precisa converter a string para o Enum conhecido ?

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

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

Ou:

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
fonte
Quero dizer, eu gostaria de converter uma debugstring em uma enumeração como: python class BuildType(Enum): debug = 200 release = 400
Vladius 31/12/16
Ótimas dicas! Está usando __dict__o mesmo que getattr? Eu estou preocupado com colisões de nomes com atributos Python interna ....
Vladius
Oh ... sim, é o mesmo que getattr. Não vejo razão para colisões de nomes. Você simplesmente não pode definir a palavra-chave como campo de classe.
ADR
4

Minha solução semelhante a Java para o problema. Espero que ajude alguém ...

    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)))
Mitch
fonte
2

Uma melhoria na resposta de @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
javed
fonte
-2

Eu só quero notificar que isso não funciona no python 3.6

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

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

Você terá que fornecer os dados como uma tupla como esta

MyEnum(('aaa',))

EDIT: Isso acaba sendo falso. Créditos a um comentarista por apontar meu erro

Sstuber
fonte
Usando o Python 3.6.6, não consegui reproduzir esse comportamento. Eu acho que você pode ter cometido um erro durante o teste (eu sei que cometi a primeira vez ao verificar isso). Se você acidentalmente colocar uma ,(vírgula) após cada elemento (como se os elementos fossem uma lista), ele tratará cada elemento como uma tupla. (isto é, a = 'aaa',é efectivamente o mesmo que a = ('aaa',))
Multihunter
Você está certo, foi um bug diferente no meu código. De alguma forma eu pensei que você precisava para colocar ,atrás de cada linha ao definir a enumeração que se transformou os valores em tuplas de alguma forma
Sstuber