Konwertuj ciąg na Enum w Pythonie

141

Zastanawiam się, jaki jest prawidłowy sposób konwersji (deserializacji) ciągu znaków na klasę Enum w języku Python. Wygląda na getattr(YourEnumType, str)to, że spełnia swoje zadanie, ale nie jestem pewien, czy jest wystarczająco bezpieczny.

Aby być bardziej szczegółowym, chciałbym przekonwertować 'debug'ciąg na obiekt Enum w następujący sposób:

class BuildType(Enum):
    debug = 200
    release = 400
Vladius
źródło

Odpowiedzi:

213

Ta funkcja jest już wbudowana w Enum [1]:

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

[1] Oficjalne dokumenty: Enum programmatic access

Ethan Furman
źródło
6
A co z wartością rezerwową na wypadek konieczności oczyszczenia danych wejściowych? Coś w tym rodzaju Build.get('illegal', Build.debug)?
Hetzroni,
1
@Hetzroni: Enumnie zawiera .get()metody, ale możesz ją dodać w razie potrzeby lub po prostu utworzyć Enumklasę bazową i zawsze dziedziczyć po niej.
Ethan Furman,
@Hetzroni: Zgodnie z zasadą "proś o wybaczenie, a nie o pozwolenie", zawsze możesz zawrzeć dostęp w klauzuli try / z wyjątkiem KeyError, aby zwrócić wartość domyślną (i jak wspomniał Ethan, opcjonalnie zawiń to we własnej funkcji / metodzie) .
Laogeodritt
1
Wyróżnienie Build('debug')
Dragonborn
2
@Dragonborn Nie zadzwoniłoby Build('debug'). Konstruktor klasy musi mieć wartość , czyli 200czy 400w tym przykładzie. Aby przekazać nazwę , musisz użyć nawiasów kwadratowych, jak już mówi odpowiedź.
Arthur Tacca
17

Inną alternatywą (szczególnie przydatna, gdy struny nie mapować 1-1 do swoich spraw enum) jest dodać staticmethoddo listy Enum, na przykład:

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

Wtedy możesz to zrobić question_type = QuestionType.from_str('singleSelect')

rogueleaderr
źródło
1
Bardzo powiązane, jeśli często to robisz: 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)

Lub musisz przekonwertować ciąg znaków na znane Enum?

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

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

Lub:

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
źródło
Chodzi mi o to, że chciałbym zamienić debugciąg na wyliczenie takich: python class BuildType(Enum): debug = 200 release = 400
Vladius
Świetne wskazówki! Czy używanie jest __dict__takie samo jak getattr? Martwię się kolizjami nazw z wewnętrznymi atrybutami Pythona ...
Vladius,
Och ... tak, to to samo co getattr. Nie widzę powodu do kolizji nazw. Po prostu nie możesz ustawić słowa kluczowego jako pola klasy.
ADR
4

Moje rozwiązanie problemu w stylu Java. Mam nadzieję, że to komuś pomoże ...

    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
źródło
2

Ulepszenie odpowiedzi @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
źródło
-2

Chcę tylko powiadomić, że to nie działa w Pythonie 3.6

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

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

Będziesz musiał podać dane jako krotkę, taką jak ta

MyEnum(('aaa',))

EDYCJA: Okazuje się, że to nieprawda. Podziękowania dla komentatora za wskazanie mojego błędu

Sstuber
źródło
Używając Pythona 3.6.6, nie mogłem odtworzyć tego zachowania. Myślę, że mogłeś popełnić błąd podczas testowania (wiem, że zrobiłem to pierwszy raz, sprawdzając to). Jeśli przypadkowo umieścisz ,(przecinek) po każdym elemencie (tak jakby elementy były listą), to potraktuje każdy element jako krotkę. (czyli a = 'aaa',właściwie to samo co a = ('aaa',))
Multihunter
Masz rację, to był inny błąd w moim kodzie. Jakoś pomyślałem, że musisz umieścić ,za każdą linią podczas definiowania wyliczenia, które w jakiś sposób zmieniło wartości w krotki
Sstuber