Ubah string menjadi Enum dengan Python


142

Saya bertanya-tanya apa cara yang benar untuk mengubah (deserializing) string ke kelas Enum Python. Sepertinya getattr(YourEnumType, str)melakukan pekerjaan, tapi saya tidak yakin apakah itu cukup aman.

Untuk lebih spesifik, saya ingin mengonversi 'debug'string ke objek Enum seperti ini:

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

Jawaban:


214

Fungsi ini sudah ada di dalam untuk Enum [1]:

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

[1] Dokumen resmi: Enum programmatic access


6
Bagaimana dengan nilai cadangan jika input perlu disanitasi? Sesuatu seperti itu Build.get('illegal', Build.debug)?
Hetzroni

1
@ Hetzroni: Enumtidak datang dengan .get()metode, tetapi Anda dapat menambahkan satu sesuai kebutuhan, atau hanya membuat Enumkelas dasar dan selalu mewarisi dari itu.
Ethan Furman

@Hetzroni: Menurut prinsip "minta maaf, bukan izin", Anda selalu dapat menyelubungi akses dalam coba / kecuali klausa KeyError untuk mengembalikan default (dan seperti yang Ethan katakan, secara opsional bungkus ini dengan fungsi / metode Anda sendiri) .
Laogeodritt

1
Sebutan terhormat Build('debug')
Dragonborn

2
@Dragonborn Tidak akan berhasil menelepon Build('debug'). Konstruktor kelas harus mengambil nilai , yaitu 200atau 400dalam contoh ini. Untuk memberikan nama, Anda harus menggunakan tanda kurung siku, seperti yang sudah dijawab oleh jawabannya.
Arthur Tacca

17

Alternatif lain (terutama berguna jika string Anda tidak peta 1-1 untuk kasus enum Anda) adalah menambahkan staticmethoduntuk Anda Enum, misalnya:

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

Maka Anda bisa melakukannya question_type = QuestionType.from_str('singleSelect')


1
Sangat terkait, jika Anda sering melakukan ini: 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)

Atau Anda perlu mengonversi string menjadi Enum yang dikenal ?

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

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

Atau:

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

Maksud saya, saya ingin mengubah debugstring menjadi enum seperti: python class BuildType(Enum): debug = 200 release = 400
Vladius

Tips hebat! Apakah menggunakan __dict__sama dengan getattr? Saya khawatir tentang tabrakan nama dengan atribut Python internal ....
Vladius

Oh ... ya sama dengan getattr. Saya tidak melihat alasan untuk tabrakan nama. Anda tidak dapat menetapkan kata kunci sebagai bidang kelas.
ADR

4

Solusi mirip Java saya untuk masalah ini. Semoga ini bisa membantu seseorang ...

    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

Peningkatan jawaban @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

Saya hanya ingin memberi tahu ini tidak bekerja di python 3.6

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

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

Anda harus memberikan data sebagai tuple seperti ini

MyEnum(('aaa',))

EDIT: Ini ternyata salah. Penghargaan kepada komentator karena menunjukkan kesalahan saya


Menggunakan Python 3.6.6, saya tidak bisa mereproduksi perilaku ini. Saya pikir Anda mungkin telah melakukan kesalahan saat pengujian (saya tahu saya lakukan pertama kali ketika memeriksa ini). Jika Anda secara tidak sengaja menempatkan ,(koma) setelah setiap elemen (seolah-olah elemen itu daftar) maka itu memperlakukan setiap elemen sebagai tupel. (Yaitu a = 'aaa',sebenarnya sama dengan a = ('aaa',))
Multihunter

Anda benar, itu bug yang berbeda dalam kode saya. Entah bagaimana saya pikir Anda perlu meletakkan di ,belakang setiap baris sambil mendefinisikan enum yang mengubah nilai menjadi tuple entah bagaimana
Sstuber
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.