Convertir cadena a Enum en Python

142

Me pregunto cuál es la forma correcta de convertir (deserializar) una cadena a una clase Enum de Python. Parece que getattr(YourEnumType, str)hace el trabajo, pero no estoy seguro de si es lo suficientemente seguro.

Solo para ser más específico, me gustaría convertir una 'debug'cadena en un objeto Enum como este:

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

Respuestas:

214

Esta funcionalidad ya está integrada en Enum [1]:

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

[1] Documentos oficiales: Enum programmatic access

Ethan Furman
fuente
66
¿Qué pasa con un valor de reserva en caso de que la entrada necesite ser desinfectada? Algo en el tipo de Build.get('illegal', Build.debug)?
Hetzroni
1
@Hetzroni: Enumno viene con un .get()método, pero puede agregar uno según sea necesario, o simplemente hacer una Enumclase base y siempre heredar de eso.
Ethan Furman
@Hetzroni: según el principio de "pedir perdón, no permiso", siempre puede envolver el acceso en una cláusula try / except KeyError para devolver el valor predeterminado (y como Ethan mencionó, opcionalmente envuelva esto en su propia función / método) .
Laogeodritt
1
Mención de honor Build('debug')
Dragonborn
2
@Dragonborn No funcionaría llamar Build('debug'). El constructor de la clase debe tomar el valor , es decir, 200o 400en este ejemplo. Para pasar el nombre debe usar corchetes, como ya dice la respuesta.
Arthur Tacca
17

Otra alternativa (especialmente útil si sus cadenas no se asignan 1-1 a sus casos de enumeración) es agregar un staticmethoda su Enum, por ejemplo:

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

Entonces puedes hacer question_type = QuestionType.from_str('singleSelect')

rogueleaderr
fuente
1
Muy relacionado, si te encuentras haciendo esto a menudo: 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)

¿O necesita convertir una cadena a Enum conocida ?

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

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

O:

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
fuente
Quiero decir que me gustaría convertir una debugcadena en una enumeración de tales: python class BuildType(Enum): debug = 200 release = 400
Vladius
Grandes consejos! ¿Está usando __dict__lo mismo que getattr? Me preocupan las colisiones de nombres con atributos internos de Python ...
Vladius
Oh ... sí, es lo mismo que getattr. No veo ninguna razón para las colisiones de nombres. Simplemente no puede establecer la palabra clave como campo de clase.
ADR
4

Mi solución al problema similar a Java. Espero que ayude a alguien ...

    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
fuente
2

Una mejora a la respuesta 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
fuente
-2

Solo quiero notificar que esto no funciona en Python 3.6

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

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

Tendrás que dar los datos como una tupla como esta

MyEnum(('aaa',))

EDITAR: Esto resulta ser falso. Créditos a un comentarista por señalar mi error

Sstuber
fuente
Usando Python 3.6.6, no pude reproducir este comportamiento. Creo que puede haber cometido un error durante la prueba (sé que lo hice la primera vez al verificar esto). Si accidentalmente coloca una ,(coma) después de cada elemento (como si los elementos fueran una lista), entonces trata a cada elemento como una tupla. ( a = 'aaa',es decir, en realidad es lo mismo que a = ('aaa',))
Multihunter
Tienes razón, fue un error diferente en mi código. De alguna manera pensé que necesitabas poner ,detrás de cada línea al definir la enumeración que de alguna manera convirtió los valores en tuplas
Sstuber