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

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


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


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

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

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

-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


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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.