Konvertieren Sie einen String in Python in Enum

138

Ich frage mich, wie man einen String richtig in eine Python-Enum-Klasse konvertiert (deserialisiert). Scheint getattr(YourEnumType, str)den Job zu erledigen , aber ich bin mir nicht sicher, ob es sicher genug ist.

Um genauer zu sein, möchte ich einen 'debug'String wie folgt in ein Enum-Objekt konvertieren :

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

Antworten:

210

Diese Funktionalität ist bereits in Enum [1] integriert:

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

[1] Offizielle Dokumente: Enum programmatic access

Ethan Furman
quelle
5
Was ist mit einem Fallback-Wert für den Fall, dass die Eingabe bereinigt werden muss? Etwas in der Art von Build.get('illegal', Build.debug)?
Hetzroni
1
@Hetzroni: Enumkommt nicht mit einer .get()Methode, aber Sie können eine nach Bedarf hinzufügen oder einfach eine Basisklasse erstellen Enumund immer davon erben.
Ethan Furman
@Hetzroni: Gemäß dem Prinzip "Bitte um Vergebung, nicht um Erlaubnis" können Sie den Zugriff immer in eine try / außer KeyError-Klausel einschließen, um die Standardeinstellung zurückzugeben (und, wie Ethan erwähnte, optional in Ihre eigene Funktion / Methode einschließen). .
Laogeodritt
1
Lobende Erwähnung Build('debug')
Dragonborn
2
@Dragonborn Es würde nicht funktionieren anzurufen Build('debug'). Der Klassenkonstruktor muss den Wert annehmen , dh 200oder 400in diesem Beispiel. Um den Namen zu übergeben , müssen Sie eckige Klammern verwenden, wie die Antwort bereits sagt.
Arthur Tacca
17

Eine andere Alternative (besonders nützlich, wenn Ihre Zeichenfolgen Ihren Aufzählungsfällen nicht 1-1 zuordnen) besteht darin, a staticmethodzu Ihren hinzuzufügen Enum, z.

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

Dann können Sie tun question_type = QuestionType.from_str('singleSelect')

rogueleaderr
quelle
1
Sehr verwandt, wenn Sie dies häufig tun: 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)

Oder müssen Sie einen String in eine bekannte Enum konvertieren ?

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

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

Oder:

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
quelle
Ich meine, ich möchte einen debugString in eine Aufzählung von solchen umwandeln : python class BuildType(Enum): debug = 200 release = 400
Vladius
Tolle Tipps! Ist __dict__das gleiche wie getattr? Ich mache mir Sorgen um Namenskollisionen mit internen Python-Attributen ....
Vladius
Oh ... ja es ist das gleiche wie getattr. Ich sehe keinen Grund für Namenskollisionen. Sie können das Schlüsselwort einfach nicht als Klassenfeld festlegen.
ADR
4

Meine Java-ähnliche Lösung für das Problem. Hoffe es hilft jemandem ...

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

Eine Verbesserung der Antwort von @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
quelle
-2

Ich möchte nur benachrichtigen, dass dies in Python 3.6 nicht funktioniert

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

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

Sie müssen die Daten als solches Tupel angeben

MyEnum(('aaa',))

EDIT: Dies stellt sich als falsch heraus. Dank an einen Kommentator für den Hinweis auf meinen Fehler

Sstuber
quelle
Mit Python 3.6.6 konnte ich dieses Verhalten nicht reproduzieren. Ich denke, Sie haben beim Testen möglicherweise einen Fehler gemacht (ich weiß, dass ich dies beim ersten Überprüfen getan habe). Wenn Sie versehentlich ein ,(Komma) nach jedem Element setzen (als ob die Elemente eine Liste wären), wird jedes Element als Tupel behandelt. (dh a = 'aaa',ist eigentlich das gleiche wie a = ('aaa',))
Multihunter
Sie haben Recht, es war ein anderer Fehler in meinem Code. Ich dachte irgendwie, Sie ,
müssten