Convertir une chaîne en Enum en Python

142

Je me demande quelle est la bonne façon de convertir (désérialiser) une chaîne en classe Enum de Python. Cela semble getattr(YourEnumType, str)faire le travail, mais je ne suis pas sûr que ce soit suffisamment sûr.

Pour être plus précis, je voudrais convertir une 'debug'chaîne en un objet Enum comme ceci:

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

Réponses:

214

Cette fonctionnalité est déjà intégrée à Enum [1]:

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

[1] Documents officiels: Enum programmatic access

Ethan Furman
la source
6
Qu'en est-il d'une valeur de secours au cas où l'entrée doit être nettoyée? Quelque chose dans le genre Build.get('illegal', Build.debug)?
Hetzroni
1
@Hetzroni: Enumne vient pas avec une .get()méthode, mais vous pouvez en ajouter une si nécessaire, ou simplement créer une Enumclasse de base et toujours en hériter.
Ethan Furman
@Hetzroni: selon le principe "demander pardon, pas pour permission", vous pouvez toujours envelopper l'accès dans une clause try / except KeyError pour renvoyer la clause par défaut (et comme Ethan l'a mentionné, éventuellement encapsuler cela dans votre propre fonction / méthode) .
Laogeodritt
1
Mention honorable Build('debug')
Dragonborn
2
@Dragonborn Cela ne marcherait pas d'appeler Build('debug'). Le constructeur de classe doit prendre la valeur , c'est 200- 400à- dire ou dans cet exemple. Pour transmettre le nom, vous devez utiliser des crochets, comme la réponse le dit déjà.
Arthur Tacca le
17

Une autre alternative (particulièrement utile si vos chaînes ne mappent pas 1-1 à vos cas d'énumération) est d'ajouter un staticmethodà votre Enum, par exemple:

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

Alors tu peux faire question_type = QuestionType.from_str('singleSelect')

rogueleaderr
la source
1
Très lié, si vous vous retrouvez à faire cela souvent: 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 vous devez convertir une chaîne en Enum connu ?

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
la source
Je veux dire, je voudrais convertir une debugchaîne en une énumération de ce type: python class BuildType(Enum): debug = 200 release = 400
Vladius
Bons conseils! Est-ce que l'utilisation est __dict__la même que getattr? Je
m'inquiète des
Oh ... oui c'est pareil getattr. Je ne vois aucune raison de conflits de noms. Vous ne pouvez tout simplement pas définir le mot clé comme champ de classe.
ADR
4

Ma solution de type Java au problème. J'espère que cela aide quelqu'un ...

    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
la source
2

Une amélioration de la réponse 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
la source
-2

Je veux juste signaler que cela ne fonctionne pas en python 3.6

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

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

Vous devrez donner les données sous forme de tuple comme celui-ci

MyEnum(('aaa',))

EDIT: Cela s'avère être faux. Crédits à un commentateur pour avoir signalé mon erreur

Sstuber
la source
En utilisant Python 3.6.6, je n'ai pas pu reproduire ce comportement. Je pense que vous avez peut-être fait une erreur lors du test (je sais que je l'ai fait la première fois en vérifiant cela). Si vous mettez accidentellement une ,(virgule) après chaque élément (comme si les éléments étaient une liste), il traite chaque élément comme un tuple. (c'est-à a = 'aaa',- dire est en fait le même que a = ('aaa',))
Multihunter
Vous avez raison, c'était un bug différent dans mon code. Je pensais que vous deviez mettre ,derrière chaque ligne tout en définissant l'énumération qui transformait les valeurs en tuples en quelque sorte
Sstuber