Converti la stringa in Enum in Python


142

Mi chiedo quale sia il modo corretto di convertire (deserializzare) una stringa in una classe Enum di Python. Sembra getattr(YourEnumType, str)che funzioni, ma non sono sicuro che sia abbastanza sicuro.

Giusto per essere più specifico, vorrei convertire una 'debug'stringa in un oggetto Enum come questo:

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

Risposte:


214

Questa funzionalità è già integrata in Enum [1]:

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

[1] Documenti ufficiali: Enum programmatic access


6
Che dire di un valore di fallback nel caso in cui l'input debba essere disinfettato? Qualcosa del genere Build.get('illegal', Build.debug)?
Hetzroni,

1
@Hetzroni: Enumnon viene fornito un .get()metodo, ma puoi aggiungerne uno secondo necessità, o semplicemente creare una Enumclasse base ed ereditare sempre da quello.
Ethan Furman,

@Hetzroni: secondo il principio "chiedi perdono, non permesso", puoi sempre avvolgere l'accesso in una clausola try / tranne KeyError per restituire il valore predefinito (e, come menzionato da Ethan, opzionalmente racchiudilo nella tua funzione / metodo) .
Laogeodritt,

1
Menzione d'onore Build('debug')
Dragonborn,

2
@Dragonborn Non avrebbe funzionato chiamare Build('debug'). Il costruttore della classe deve assumere il valore , ovvero 200o 400in questo esempio. Per passare il nome è necessario utilizzare parentesi quadre, come già indicato nella risposta.
Arthur Tacca,

17

Un'altra alternativa (particolarmente utile se le stringhe non corrispondono a 1-1 per i casi enum) è quella di aggiungere una staticmethodalla tua Enum, ad esempio:

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

Quindi puoi farlo question_type = QuestionType.from_str('singleSelect')


1
Molto legato, se ti ritrovi a farlo spesso: 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)

Oppure devi convertire una stringa in Enum noto ?

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

Voglio dire, vorrei convertire una debugstringa in un enum di tale: python class BuildType(Enum): debug = 200 release = 400
Vladius

Ottimi consigli! Sta usando __dict__lo stesso di getattr? Mi preoccupo delle collisioni di nomi con attributi interni di Python ....
Vladius

Oh ... sì, è lo stesso di getattr. Non vedo alcun motivo per le collisioni di nomi. Non puoi semplicemente impostare la parola chiave come campo di classe.
ADR

4

La mia soluzione simile a Java al problema. Spero che aiuti qualcuno ...

    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

Un miglioramento alla risposta di @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

Voglio solo avvisare che non funziona in Python 3.6

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

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

Dovrai fornire i dati come una tupla come questa

MyEnum(('aaa',))

EDIT: questo risulta essere falso. Ringraziamenti a un commentatore per aver segnalato il mio errore


Usando Python 3.6.6, non sono riuscito a riprodurre questo comportamento. Penso che potresti aver fatto un errore durante il test (so di averlo fatto la prima volta durante il controllo). Se si inserisce accidentalmente una ,(virgola) dopo ogni elemento (come se gli elementi fossero un elenco), ogni elemento viene trattato come una tupla. (cioè a = 'aaa',è effettivamente lo stesso di a = ('aaa',))
Multihunter

Hai ragione, era un bug diverso nel mio codice. In qualche modo pensavo che dovessi mettere ,dietro ogni linea mentre definivi l'enum che in qualche modo trasformava i valori in tuple
Sstuber
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.