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

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


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


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

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

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

-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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.