Bagaimana saya bisa mewakili 'Enum' dengan Python?

1143

Saya terutama adalah pengembang C #, tetapi saat ini saya sedang mengerjakan proyek dengan Python.

Bagaimana saya bisa mewakili yang setara dengan Enum dengan Python?

sekte
sumber

Jawaban:

2689

Enum telah ditambahkan ke Python 3.4 seperti yang dijelaskan dalam PEP 435 . Ini juga telah di- backport ke 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, dan 2.4 di pypi.

Untuk teknik Enum yang lebih lanjut, coba perpustakaan aenum (2.7, 3.3+, penulis yang sama dengan enum34. Code tidak sepenuhnya kompatibel antara py2 dan py3, mis. Anda akan membutuhkan __order__python 2 ).

  • Untuk menggunakan enum34, lakukan$ pip install enum34
  • Untuk menggunakan aenum, lakukan$ pip install aenum

Menginstal enum(tidak ada angka) akan menginstal versi yang sama sekali berbeda dan tidak kompatibel.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

atau yang setara:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

Dalam versi sebelumnya, satu cara untuk mencapai enum adalah:

def enum(**enums):
    return type('Enum', (), enums)

yang digunakan seperti ini:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

Anda juga dapat dengan mudah mendukung pencacahan otomatis dengan sesuatu seperti ini:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

dan digunakan seperti ini:

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

Dukungan untuk mengonversi nilai kembali ke nama dapat ditambahkan dengan cara ini:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

Ini menimpa apa pun dengan nama itu, tetapi ini berguna untuk menampilkan enum Anda menjadi keluaran. Ini akan melempar KeyError jika pemetaan terbalik tidak ada. Dengan contoh pertama:

>>> Numbers.reverse_mapping['three']
'THREE'
Alec Thomas
sumber
1
Saya tidak bisa mengerti, mengapa mereka melewatkan kwargs (** name) dalam metode enum (* sequential, ** named)? Tolong jelaskan. Tanpa kwarg juga akan berfungsi. Saya memeriksanya.
Seenu S
Akan lebih baik untuk memperbarui fungsi Python 2 agar kompatibel dengan API fungsional Enum Python 3 (nama, nilai)
bscan
The vw kwargs ( **named) dalam fungsi enum untuk versi yang lebih lama adalah untuk mendukung nilai-nilai khusus:enum("blue", "red", "green", black=0)
Éric Araujo
823

Sebelum PEP 435, Python tidak memiliki padanan tapi Anda bisa menerapkannya sendiri.

Saya sendiri, saya suka menjaganya tetap sederhana (saya telah melihat beberapa contoh rumit yang mengerikan di internet), sesuatu seperti ini ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Di Python 3.4 ( PEP 435 ), Anda bisa membuat Enum kelas dasar. Ini memberi Anda sedikit fungsi tambahan, dijelaskan dalam PEP. Sebagai contoh, anggota enum berbeda dari bilangan bulat, dan mereka terdiri dari a namedan a value.

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

Jika Anda tidak ingin mengetik nilai, gunakan pintasan berikut:

class Animal(Enum):
    DOG, CAT = range(2)

Enumimplementasi dapat dikonversi menjadi daftar dan dapat diubah . Urutan anggotanya adalah perintah deklarasi dan tidak ada hubungannya dengan nilai-nilai mereka. Sebagai contoh:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True
Jundiaius
sumber
51
Tidak, ini adalah variabel kelas.
Georg Schölly
246
Python dinamis secara default. Tidak ada alasan yang sah untuk menerapkan keamanan waktu kompilasi dalam bahasa seperti Python, terutama ketika tidak ada. Dan satu hal lagi ... pola yang baik hanya baik dalam konteks di mana ia diciptakan. Pola yang baik juga bisa digantikan atau sama sekali tidak berguna, tergantung pada alat yang Anda gunakan.
Alexandru Nedelcu
20
@ Longpoke jika Anda memiliki 100 nilai, maka Anda pasti melakukan sesuatu yang salah;) Saya suka angka yang terkait dengan enum saya ... mereka mudah ditulis (vs string), dapat dengan mudah disimpan dalam database, dan kompatibel dengan C / C ++ enum, yang membuat proses marshaling menjadi lebih mudah.
Alexandru Nedelcu
50
Saya menggunakan ini, dengan nomor diganti oleh object().
Tobu
9
PEP354 asli tidak lagi hanya ditolak, tetapi sekarang ditandai digantikan. PEP435 menambahkan Enum standar untuk Python 3.4. Lihat python.org/dev/peps/pep-0435
Peter Hansen
323

Berikut ini satu implementasinya:

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

Berikut ini adalah penggunaannya:

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)
shahjapan
sumber
51
Luar biasa. Ini dapat lebih ditingkatkan dengan mengganti __setattr__(self, name, value)dan mungkin __delattr__(self, name)sehingga jika Anda secara tidak sengaja menulis Animals.DOG = CAT, itu tidak akan berhasil secara diam-diam.
Joonas Pulakka
15
@ Shahjapan: Menarik, tetapi relatif lambat: tes dilakukan untuk setiap akses seperti Animals.DOG; juga, nilai-nilai konstanta adalah string, sehingga perbandingan dengan konstanta ini lebih lambat daripada jika, katakanlah, bilangan bulat diizinkan sebagai nilai.
Eric O Lebigot
3
@ Shahjapan: Saya berpendapat bahwa solusi ini tidak terbaca seperti solusi pendek Alexandru atau Mark, misalnya. Ini solusi yang menarik. :)
Eric O Lebigot
Saya mencoba menggunakan setattr()fungsi di dalam __init__()metode alih-alih __getattr__()metode overidding . Saya berasumsi ini seharusnya bekerja dengan cara yang sama: class Enum (objek): def __init __ (self, enum_string_list): if type (enum_string_list) == list: for enum_string in enum_string_list: setattr (self, enum_string, enum_string) lain: meningkatkan AttributeError
Harshith JV
8
@ AndréTerra: bagaimana Anda memeriksa set keanggotaan dalam suatu try-exceptblok?
bgusach
210

Jika Anda membutuhkan nilai numerik, inilah cara tercepat:

dog, cat, rabbit = range(3)

Di Python 3.x Anda juga bisa menambahkan placeholder yang berkilau bintangnya di akhir, yang akan menyerap semua nilai yang tersisa dari rentang jika Anda tidak keberatan membuang-buang memori dan tidak dapat menghitung:

dog, cat, rabbit, horse, *_ = range(100)
Mark Harrison
sumber
1
Tapi ini mungkin membutuhkan lebih banyak memori!
MJ
Saya tidak melihat titik dari placeholder berbintang yang diberikan bahwa Python akan memeriksa jumlah nilai untuk dibuka (sehingga akan melakukan penghitungan untuk Anda).
Gabriel Devillers
@GabrielDevillers, saya pikir Python akan menaikkan pengecualian jika ada ketidakcocokan pada sejumlah elemen dalam tuple untuk menetapkan.
Mark Harrison
1
Memang, itu dalam pengujian saya (Python2,3) tetapi itu berarti bahwa kesalahan penghitungan dari programmer akan ditangkap pada tes pertama (dengan pesan memberikan jumlah yang benar).
Gabriel Devillers
1
Saya tidak bisa menghitung. Bisakah placeholder yang berkilau bintangnya juga memperbaiki keuangan saya?
javadba
131

Solusi terbaik untuk Anda akan tergantung pada apa yang Anda butuhkan dari kepalsuan Anda enum.

Enum sederhana:

Jika Anda enumhanya memerlukan daftar nama yang mengidentifikasi item yang berbeda , solusinya oleh Mark Harrison (di atas) sangat bagus:

Pen, Pencil, Eraser = range(0, 3)

Menggunakan rangejuga memungkinkan Anda untuk menetapkan nilai awal :

Pen, Pencil, Eraser = range(9, 12)

Selain hal-hal di atas, jika Anda juga mensyaratkan bahwa barang-barang itu milik wadah semacam, maka sematkan di kelas:

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

Untuk menggunakan item enum, Anda sekarang harus menggunakan nama kontainer dan nama item:

stype = Stationery.Pen

Enum kompleks:

Untuk daftar panjang enum atau penggunaan enum yang lebih rumit, solusi ini tidak akan cukup. Anda bisa melihat resep oleh Will Ware untuk Mensimulasikan Enumerasi dengan Python yang diterbitkan dalam Python Cookbook . Versi online yang tersedia di sini .

Info lebih lanjut:

PEP 354: Pencacahan dengan Python memiliki detail menarik dari proposal enum dengan Python dan mengapa ditolak.

Ashwin
sumber
7
dengan rangeAnda dapat menghilangkan argumen pertama jika 0
ToonAlfrink
Enum palsu lain yang sesuai dengan beberapa tujuan adalah my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Kemudian my_enumdapat digunakan dalam pencarian, misalnya, my_enum['Item0']dapat menjadi indeks menjadi suatu urutan. Anda mungkin ingin membungkus hasil str.splitdalam fungsi yang melempar pengecualian jika ada duplikat.
Ana Nimbus
Bagus! Untuk Bendera, Anda dapatFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx
Ini adalah jawaban terbaik
Yuriy Pozniak
78

Pola typesafe enum yang digunakan di Java pra-JDK 5 memiliki sejumlah keunggulan. Sama seperti dalam jawaban Alexandru, Anda membuat kelas dan bidang level kelas adalah nilai enum; Namun, nilai enum adalah instance dari kelas daripada bilangan bulat kecil. Ini memiliki keuntungan bahwa nilai enum Anda tidak secara tidak sengaja membandingkan sama dengan bilangan bulat kecil, Anda dapat mengontrol bagaimana mereka dicetak, menambahkan metode arbitrer jika itu berguna dan membuat pernyataan menggunakan isinstance:

class Animal:
   def __init__(self, name):
       self.name = name

   def __str__(self):
       return self.name

   def __repr__(self):
       return "<Animal: %s>" % self

Animal.DOG = Animal("dog")
Animal.CAT = Animal("cat")

>>> x = Animal.DOG
>>> x
<Animal: dog>
>>> x == 1
False

Sebuah utas terbaru tentang python-dev menunjukkan ada beberapa perpustakaan enum di alam liar, termasuk:

Aaron Maenpaa
sumber
16
Saya pikir ini pendekatan yang sangat buruk. Animal.DOG = Animal ("dog") Animal.DOG2 = Animal ("dog") menyatakan Animal.DOG == Animal.DOG2 gagal ...
Kebingungan
11
@Konfusi Pengguna tidak seharusnya memanggil konstruktor, fakta bahwa bahkan ada konstruktor adalah detail implementasi dan Anda harus berkomunikasi dengan siapa yang pernah menggunakan kode Anda yang membuat nilai enumerasi baru tidak masuk akal dan kode yang keluar tidak akan "lakukan hal yang benar". Tentu saja itu tidak menghentikan Anda untuk mengimplementasikan Animal.from_name ("dog") -> Animal.DOG.
Aaron Maenpaa
13
"keuntungan yang nilai enum Anda tidak secara tidak sengaja membandingkan sama dengan bilangan bulat kecil" Apa keuntungannya? Apa yang salah dengan membandingkan enum Anda dengan bilangan bulat? Terutama jika Anda menyimpan enum dalam database, Anda biasanya ingin itu disimpan sebagai bilangan bulat, jadi Anda harus membandingkannya dengan bilangan bulat di beberapa titik.
ibz
3
@Aaaron Maenpaa. benar. Itu masih merupakan cara yang rusak dan terlalu rumit untuk melakukannya.
aaronasterling
4
@AaronMcSmooth Itu benar-benar tergantung pada apakah Anda datang dari perspektif C "Enums hanya nama untuk beberapa int" atau pendekatan yang lebih berorientasi objek di mana nilai enum adalah objek aktual dan memiliki metode (seperti bagaimana enum di Jawa 1.5 are, dan untuk tipe pola safe enum). Secara pribadi, saya tidak suka beralih pernyataan jadi saya condong ke nilai enum yang merupakan objek aktual.
Aaron Maenpaa
61

Kelas Enum dapat berupa one-liner.

class Enum(tuple): __getattr__ = tuple.index

Cara menggunakannya (memajukan dan membalikkan pencarian, kunci, nilai, item, dll.)

>>> State = Enum(['Unclaimed', 'Claimed'])
>>> State.Claimed
1
>>> State[1]
'Claimed'
>>> State
('Unclaimed', 'Claimed')
>>> range(len(State))
[0, 1]
>>> [(k, State[k]) for k in range(len(State))]
[(0, 'Unclaimed'), (1, 'Claimed')]
>>> [(k, getattr(State, k)) for k in State]
[('Unclaimed', 0), ('Claimed', 1)]
Zoetic
sumber
Saya pikir itu adalah solusi paling elegan ujung sederhana. Dalam python 2.4 (ya, server lama) tuple belum diindeks. Saya memutuskan untuk mengganti dengan daftar.
Massimo
Saya mencoba ini di notebook Jupyter dan menemukan bahwa itu tidak akan berfungsi sebagai definisi satu baris, tetapi menempatkan definisi getattr pada baris kedua (lekukan) akan diterima.
user5920660
Solusi ini mari saya gunakan inkata kunci untuk mencari anggota yang rapi. Contoh penggunaan:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini
51

Jadi saya setuju. Mari kita tidak menegakkan keamanan jenis dengan Python, tapi saya ingin melindungi diri saya dari kesalahan konyol. Jadi apa yang kita pikirkan tentang ini?

class Animal(object):
    values = ['Horse','Dog','Cat']

    class __metaclass__(type):
        def __getattr__(self, name):
            return self.values.index(name)

Itu menjaga saya dari tabrakan nilai dalam mendefinisikan enum saya.

>>> Animal.Cat
2

Ada keuntungan lain yang berguna: pencarian terbalik sangat cepat:

def name_of(self, i):
    return self.values[i]
royal
sumber
Saya suka ini, tetapi Anda mungkin juga mengunci nilai untuk efisiensi dengan tuple? Saya bermain-main dengannya dan muncul dengan versi yang menetapkan nilai-nilai diri dari args in init . Senang bisa mendeklarasikan Animal = Enum('horse', 'dog', 'cat'). Saya juga menangkap ValueError di getattr jika ada item yang hilang di self.values ​​- sepertinya lebih baik untuk meningkatkan AttributeError dengan string nama yang disediakan. Saya tidak bisa membuat metaclass bekerja di Python 2.7 berdasarkan pengetahuan saya yang terbatas di area itu, tetapi kelas kustom Enum saya berfungsi dengan baik dengan metode instan.
trojjer
49

Python tidak memiliki built-in yang setara dengan enum, dan jawaban lain memiliki ide untuk mengimplementasikannya sendiri (Anda mungkin juga tertarik dengan versi over the top di buku masak Python).

Namun, dalam situasi di mana sebuah enumakan dipanggil dalam C, saya biasanya berakhir hanya menggunakan string sederhana : karena cara objek / atribut diimplementasikan, (C) Python dioptimalkan untuk bekerja sangat cepat dengan string pendek, jadi tidak akan benar-benar bermanfaat untuk menggunakan bilangan bulat. Untuk menjaga dari kesalahan ketik / nilai tidak valid Anda dapat memasukkan cek di tempat-tempat yang dipilih.

ANIMALS = ['cat', 'dog', 'python']

def take_for_a_walk(animal):
    assert animal in ANIMALS
    ...

(Satu kelemahan dibandingkan menggunakan kelas adalah Anda kehilangan manfaat autocomplete)

dF.
sumber
2
Saya lebih suka solusi ini. Saya suka menggunakan tipe bawaan jika memungkinkan.
Seun Osewa
Versi itu tidak benar-benar berlebihan. Itu hanya memiliki banyak kode pengujian yang disediakan
Casebash
1
Sebenarnya, versi "benar" ada di komentar dan jauh lebih kompleks - versi utama memiliki bug kecil.
Casebash
39

Pada 2013-05-10, Guido setuju untuk menerima PEP 435 ke dalam pustaka standar Python 3.4. Ini berarti bahwa Python akhirnya telah membangun dukungan untuk enumerasi!

Ada backport yang tersedia untuk Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, dan 2.4. Ada di Pypi sebagai enum34 .

Pernyataan:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

Perwakilan:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

Pengulangan:

>>> for color in Color:
...   print(color)
...
Color.red
Color.green
Color.blue

Akses terprogram:

>>> Color(1)
Color.red
>>> Color['blue']
Color.blue

Untuk informasi lebih lanjut, lihat proposal . Dokumentasi resmi mungkin akan segera menyusul.

Danilo Bargen
sumber
33

Saya lebih suka mendefinisikan enum dengan Python seperti ini:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Ini lebih tahan bug daripada menggunakan bilangan bulat karena Anda tidak perlu khawatir tentang memastikan bilangan bulat itu unik (mis. Jika Anda mengatakan Dog = 1 dan Cat = 1 Anda akan mengacaukannya).

Ini lebih tahan bug daripada menggunakan string karena Anda tidak perlu khawatir tentang kesalahan ketik (mis. X == "catt" gagal diam-diam, tetapi x == Animal.Catt adalah pengecualian runtime).

mbac32768
sumber
31
def M_add_class_attribs(attribs):
    def foo(name, bases, dict_):
        for v, k in attribs:
            dict_[k] = v
        return type(name, bases, dict_)
    return foo

def enum(*names):
    class Foo(object):
        __metaclass__ = M_add_class_attribs(enumerate(names))
        def __setattr__(self, name, value):  # this makes it read-only
            raise NotImplementedError
    return Foo()

Gunakan seperti ini:

Animal = enum('DOG', 'CAT')
Animal.DOG # returns 0
Animal.CAT # returns 1
Animal.DOG = 2 # raises NotImplementedError

jika Anda hanya ingin simbol yang unik dan tidak peduli dengan nilainya, ganti baris ini:

__metaclass__ = M_add_class_attribs(enumerate(names))

dengan ini:

__metaclass__ = M_add_class_attribs((object(), name) for name in names)
pengguna18695
sumber
11
IMHO itu akan menjadi lebih bersih jika Anda berubah enum(names)ke enum(*names)- maka Anda bisa drop kurung tambahan ketika menyebutnya.
Chris Lutz
Saya suka pendekatan ini. Saya benar-benar mengubahnya untuk mengatur nilai atribut ke string yang sama dengan nama, yang memiliki properti bagus yang Animal.DOG == 'DOG', sehingga mereka mengikatkan diri mereka untuk Anda secara otomatis. (Sangat membantu untuk mencetak keluaran debug.)
Ted Mielczarek
23

Dari Python 3.4 akan ada dukungan resmi untuk enum. Anda dapat menemukan dokumentasi dan contoh di sini di halaman dokumentasi Python 3.4 .

Enumerasi dibuat menggunakan sintaks kelas, yang membuatnya mudah dibaca dan ditulis. Metode pembuatan alternatif dijelaskan dalam API Fungsional. Untuk mendefinisikan enumerasi, subkelas Enum sebagai berikut:

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3
Cyril Gandon
sumber
Porting kembali sekarang didukung juga. Ini cara untuk pergi.
srock
22

Hmmm ... Saya kira hal yang paling dekat dengan enum adalah kamus, didefinisikan seperti ini:

months = {
    'January': 1,
    'February': 2,
    ...
}

atau

months = dict(
    January=1,
    February=2,
    ...
)

Kemudian, Anda dapat menggunakan nama simbolis untuk konstanta seperti ini:

mymonth = months['January']

Ada opsi lain, seperti daftar tupel, atau tupel tupel, tetapi kamus adalah satu-satunya yang memberi Anda cara "simbolik" (string konstan) untuk mengakses nilai.

Sunting: Saya juga menyukai jawaban Alexandru!

dguaraglia
sumber
Dan yang paling penting, Anda dapat dengan mudah beralih ke kamus jika Anda perlu mengakses nilainya seperti Anda membutuhkan nilai stringnya muncul sebagai item kotak kombo. Jadi gunakan kamus sebagai pengganti enumerasi.
LEMUEL ADANE
22

Lain, sangat sederhana, implementasi enum dengan Python, menggunakan namedtuple:

from collections import namedtuple

def enum(*keys):
    return namedtuple('Enum', keys)(*keys)

MyEnum = enum('FOO', 'BAR', 'BAZ')

atau, sebagai alternatif,

# With sequential number values
def enum(*keys):
    return namedtuple('Enum', keys)(*range(len(keys)))

# From a dict / keyword args
def enum(**kwargs):
    return namedtuple('Enum', kwargs.keys())(*kwargs.values())

Seperti metode di atas subclass itu set, ini memungkinkan:

'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO

Tetapi memiliki lebih banyak fleksibilitas karena dapat memiliki kunci dan nilai yang berbeda. Ini memungkinkan

MyEnum.FOO < MyEnum.BAR

untuk bertindak seperti yang diharapkan jika Anda menggunakan versi yang mengisi nilai angka berurutan.

agf
sumber
20

Apa yang saya gunakan:

class Enum(object):
    def __init__(self, names, separator=None):
        self.names = names.split(separator)
        for value, name in enumerate(self.names):
            setattr(self, name.upper(), value)
    def tuples(self):
        return tuple(enumerate(self.names))

Cara Penggunaan:

>>> state = Enum('draft published retracted')
>>> state.DRAFT
0
>>> state.RETRACTED
2
>>> state.FOO
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
AttributeError: 'Enum' object has no attribute 'FOO'
>>> state.tuples()
((0, 'draft'), (1, 'published'), (2, 'retracted'))

Jadi ini memberi Anda konstanta integer seperti state.PUBLISHED dan dua-tupel untuk digunakan sebagai pilihan dalam model Django.

Luciano Ramalho
sumber
17

davidg merekomendasikan menggunakan dikt. Saya akan melangkah lebih jauh dan menggunakan set:

months = set('January', 'February', ..., 'December')

Sekarang Anda dapat menguji apakah suatu nilai cocok dengan salah satu nilai dalam set seperti ini:

if m in months:

seperti dF, meskipun, saya biasanya hanya menggunakan konstanta string di tempat enum.

tuksedo
sumber
ya !, jauh lebih baik jika Anda mewarisi set dan menyediakan metode getattr !
shahjapan
17

Sederhana saja:

class Enum(object): 
    def __init__(self, tupleList):
            self.tupleList = tupleList

    def __getattr__(self, name):
            return self.tupleList.index(name)

Kemudian:

DIRECTION = Enum(('UP', 'DOWN', 'LEFT', 'RIGHT'))
DIRECTION.DOWN
1
bahaya89
sumber
16

Ini yang terbaik yang pernah saya lihat: "Enum Kelas Satu dengan Python"

http://code.activestate.com/recipes/413486/

Ini memberi Anda kelas, dan kelas berisi semua enum. Enum dapat dibandingkan satu sama lain, tetapi tidak memiliki nilai tertentu; Anda tidak dapat menggunakannya sebagai nilai integer. (Saya menolak ini pada awalnya karena saya terbiasa dengan C enum, yang merupakan nilai integer. Tetapi jika Anda tidak dapat menggunakannya sebagai integer, Anda tidak dapat menggunakannya sebagai integer karena kesalahan sehingga secara keseluruhan saya pikir ini adalah kemenangan .) Setiap enum adalah nilai unik. Anda dapat mencetak enum, Anda dapat mengulanginya, Anda dapat menguji bahwa nilai enum "di" enum. Cukup lengkap dan apik.

Sunting (cfi): Tautan di atas tidak kompatibel dengan Python 3. Ini port enum.py saya ke Python 3:

def cmp(a,b):
   if a < b: return -1
   if b < a: return 1
   return 0


def Enum(*names):
   ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!

   class EnumClass(object):
      __slots__ = names
      def __iter__(self):        return iter(constants)
      def __len__(self):         return len(constants)
      def __getitem__(self, i):  return constants[i]
      def __repr__(self):        return 'Enum' + str(names)
      def __str__(self):         return 'enum ' + str(constants)

   class EnumValue(object):
      __slots__ = ('__value')
      def __init__(self, value): self.__value = value
      Value = property(lambda self: self.__value)
      EnumType = property(lambda self: EnumType)
      def __hash__(self):        return hash(self.__value)
      def __cmp__(self, other):
         # C fans might want to remove the following assertion
         # to make all enums comparable by ordinal value {;))
         assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"
         return cmp(self.__value, other.__value)
      def __lt__(self, other):   return self.__cmp__(other) < 0
      def __eq__(self, other):   return self.__cmp__(other) == 0
      def __invert__(self):      return constants[maximum - self.__value]
      def __nonzero__(self):     return bool(self.__value)
      def __repr__(self):        return str(names[self.__value])

   maximum = len(names) - 1
   constants = [None] * len(names)
   for i, each in enumerate(names):
      val = EnumValue(i)
      setattr(EnumClass, each, val)
      constants[i] = val
   constants = tuple(constants)
   EnumType = EnumClass()
   return EnumType


if __name__ == '__main__':
   print( '\n*** Enum Demo ***')
   print( '--- Days of week ---')
   Days = Enum('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su')
   print( Days)
   print( Days.Mo)
   print( Days.Fr)
   print( Days.Mo < Days.Fr)
   print( list(Days))
   for each in Days:
      print( 'Day:', each)
   print( '--- Yes/No ---')
   Confirmation = Enum('No', 'Yes')
   answer = Confirmation.No
   print( 'Your answer is not', ~answer)
cfi
sumber
Resep ini digunakan sebagai dasar untuk PEP, yang ditolak. python.org/dev/peps/pep-0354 Satu ekstensi yang saya suka: nilai enum harus memiliki variabel anggota yang memungkinkan Anda mengeluarkan nilai integer internal. Seharusnya tidak mungkin untuk melemparkan enum ke bilangan bulat karena kesalahan, sehingga .__int__()metode harus memunculkan pengecualian untuk enum; tetapi harus ada cara untuk mendapatkan nilai. Dan harus dimungkinkan untuk menetapkan nilai integer spesifik pada waktu definisi kelas, sehingga Anda dapat menggunakan enum untuk hal-hal seperti konstanta dalam statmodul.
steveha
14

Saya memiliki kesempatan untuk membutuhkan kelas Enum, untuk tujuan decoding format file biner. Fitur yang kebetulan saya inginkan adalah definisi enum ringkas, kemampuan untuk secara bebas membuat instance enum dengan nilai integer atau string, dan represensiasi yang bermanfaat . Inilah yang akhirnya saya dapatkan:

>>> class Enum(int):
...     def __new__(cls, value):
...         if isinstance(value, str):
...             return getattr(cls, value)
...         elif isinstance(value, int):
...             return cls.__index[value]
...     def __str__(self): return self.__name
...     def __repr__(self): return "%s.%s" % (type(self).__name__, self.__name)
...     class __metaclass__(type):
...         def __new__(mcls, name, bases, attrs):
...             attrs['__slots__'] = ['_Enum__name']
...             cls = type.__new__(mcls, name, bases, attrs)
...             cls._Enum__index = _index = {}
...             for base in reversed(bases):
...                 if hasattr(base, '_Enum__index'):
...                     _index.update(base._Enum__index)
...             # create all of the instances of the new class
...             for attr in attrs.keys():
...                 value = attrs[attr]
...                 if isinstance(value, int):
...                     evalue = int.__new__(cls, value)
...                     evalue._Enum__name = attr
...                     _index[value] = evalue
...                     setattr(cls, attr, evalue)
...             return cls
... 

Contoh aneh menggunakannya:

>>> class Citrus(Enum):
...     Lemon = 1
...     Lime = 2
... 
>>> Citrus.Lemon
Citrus.Lemon
>>> 
>>> Citrus(1)
Citrus.Lemon
>>> Citrus(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __new__
KeyError: 5
>>> class Fruit(Citrus):
...     Apple = 3
...     Banana = 4
... 
>>> Fruit.Apple
Fruit.Apple
>>> Fruit.Lemon
Citrus.Lemon
>>> Fruit(1)
Citrus.Lemon
>>> Fruit(3)
Fruit.Apple
>>> "%d %s %r" % ((Fruit.Apple,)*3)
'3 Apple Fruit.Apple'
>>> Fruit(1) is Citrus.Lemon
True

Fitur utama:

  • str(), int()dan repr()semua menghasilkan keluaran paling berguna yang mungkin, masing-masing nama enumartion, nilai integernya, dan ekspresi Python yang mengevaluasi kembali ke enumerasi.
  • Nilai yang dirinci yang dikembalikan oleh konstruktor terbatas hanya pada nilai yang telah ditentukan, tidak ada nilai enum yang tidak disengaja.
  • Nilai yang dihitung adalah lajang; mereka dapat secara ketat dibandingkan denganis
TokenMacGuy
sumber
Saya sangat suka menggunakan superclass dengan metaclass sendiri, untuk membuatnya mudah untuk mendefinisikan enum. Apa yang hilang di sini adalah metode __contains__. Saya ingin dapat memeriksa bahwa variabel yang diberikan adalah bagian dari enum - terutama karena saya ingin enum untuk nilai yang diijinkan dari parameter fungsi.
xorsyst
Ini sebenarnya versi yang sedikit dipangkas dari yang saya buat awalnya (yang dapat Anda temukan di sini: enum_strict.py ) v yang mendefinisikan __instancecheck__metode. Kelas bukan kumpulan instance, jadi 1 in Fruittidak masuk akal. Namun, versi tertaut mendukung isinstance(1, Fruit)yang akan lebih benar dalam hal pengertian kelas dan instance.
SingleNegationElimination
Tetapi melupakan kelas dan berpikir dalam hal enum, maka masuk akal untuk menganggapnya sebagai koleksi. Sebagai contoh, saya mungkin memiliki enum mode pembukaan file (MODE.OPEN, MODE.WRITE, dll). Saya ingin memverifikasi parameter ke fungsi saya: jika mode dalam MODE: membaca jauh lebih baik daripada isintance (mode, Mode)
xorsyst
Saya telah memasang sesuatu yang sangat mirip, yang mendukung lebih dari sekadar int, dan didokumentasikan dan diuji, di GitHub: github.com/hmeine/named_constants
hans_meine
12

Standar baru dalam Python adalah PEP 435 , sehingga kelas Enum akan tersedia dalam versi Python mendatang:

>>> from enum import Enum

Namun untuk mulai menggunakannya sekarang, Anda dapat menginstal perpustakaan asli yang memotivasi PEP:

$ pip install flufl.enum

Kemudian Anda dapat menggunakannya sesuai panduan online-nya :

>>> from flufl.enum import Enum
>>> class Colors(Enum):
...     red = 1
...     green = 2
...     blue = 3
>>> for color in Colors: print color
Colors.red
Colors.green
Colors.blue
Riaz Rizvi
sumber
10
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Jika Anda beri nama, itu masalah Anda, tetapi jika tidak membuat objek, bukan nilai, Anda dapat melakukan ini:

>>> DOG = enum('BARK', 'WALK', 'SIT')
>>> CAT = enum('MEOW', 'WALK', 'SIT')
>>> DOG.WALK == CAT.WALK
False

Saat menggunakan implementasi lain yang diletakkan di sini (juga saat menggunakan contoh bernama dalam contoh saya), Anda harus yakin Anda tidak pernah mencoba membandingkan objek dari enum yang berbeda. Untuk inilah kemungkinan perangkap:

>>> DOG = enum('BARK'=1, 'WALK'=2, 'SIT'=3)
>>> CAT = enum('WALK'=1, 'SIT'=2)
>>> pet1_state = DOG.BARK
>>> pet2_state = CAT.WALK
>>> pet1_state == pet2_state
True

Astaga!

estani
sumber
9

Saya sangat menyukai solusi Alec Thomas (http://stackoverflow.com/a/1695250):

def enum(**enums):
    '''simple constant "enums"'''
    return type('Enum', (object,), enums)

Ini terlihat elegan dan bersih, tetapi hanya fungsi yang menciptakan kelas dengan atribut yang ditentukan.

Dengan sedikit modifikasi pada fungsi, kita bisa membuatnya bertindak sedikit lebih 'enumy':

CATATAN: Saya membuat contoh berikut dengan mencoba mereproduksi perilaku gaya baru 'enum' pygtk (seperti Gtk.MessageType.WARNING)

def enum_base(t, **enums):
    '''enums with a base class'''
    T = type('Enum', (t,), {})
    for key,val in enums.items():
        setattr(T, key, T(val))

    return T

Ini menciptakan enum berdasarkan jenis tertentu. Selain memberikan akses atribut seperti fungsi sebelumnya, ia berperilaku seperti yang Anda harapkan dari Enum sehubungan dengan tipe. Itu juga mewarisi kelas dasar.

Misalnya, integer enums:

>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)
>>> Numbers.ONE
1
>>> x = Numbers.TWO
>>> 10 + x
12
>>> type(Numbers)
<type 'type'>
>>> type(Numbers.ONE)
<class 'Enum'>
>>> isinstance(x, Numbers)
True

Hal menarik lain yang dapat dilakukan dengan metode ini adalah menyesuaikan perilaku spesifik dengan mengganti metode bawaan:

def enum_repr(t, **enums):
    '''enums with a base class and repr() output'''
    class Enum(t):
        def __repr__(self):
            return '<enum {0} of type Enum({1})>'.format(self._name, t.__name__)

    for key,val in enums.items():
        i = Enum(val)
        i._name = key
        setattr(Enum, key, i)

    return Enum



>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)
>>> repr(Numbers.ONE)
'<enum ONE of type Enum(int)>'
>>> str(Numbers.ONE)
'1'
bj0
sumber
ide tipe "dasar" ini rapi :)
MestreLion
ya, perhatikan bahwa Anda juga dapat melakukan ini dengan Python 3.4 Enum baru: python.org/dev/peps/pep-0435/#other-derived-enumerations
bj0
7

Paket enum dari PyPI menyediakan implementasi enum yang kuat. Jawaban sebelumnya menyebutkan PEP 354; ini ditolak tetapi proposal itu diterapkan http://pypi.python.org/pypi/enum .

Penggunaannya mudah dan elegan:

>>> from enum import Enum
>>> Colors = Enum('red', 'blue', 'green')
>>> shirt_color = Colors.green
>>> shirt_color = Colors[2]
>>> shirt_color > Colors.red
True
>>> shirt_color.index
2
>>> str(shirt_color)
'green'
Ashwin
sumber
5

Saran Alexandru untuk menggunakan konstanta kelas untuk enum bekerja dengan sangat baik.

Saya juga ingin menambahkan kamus untuk setiap rangkaian konstanta untuk mencari representasi string yang dapat dibaca manusia.

Ini melayani dua tujuan: a) menyediakan cara sederhana untuk mencetak enum Anda dengan cantik dan b) kamus secara logis mengelompokkan konstanta sehingga Anda dapat menguji keanggotaan.

class Animal:    
  TYPE_DOG = 1
  TYPE_CAT = 2

  type2str = {
    TYPE_DOG: "dog",
    TYPE_CAT: "cat"
  }

  def __init__(self, type_):
    assert type_ in self.type2str.keys()
    self._type = type_

  def __repr__(self):
    return "<%s type=%s>" % (
        self.__class__.__name__, self.type2str[self._type].upper())
Rick Harris
sumber
5

Inilah pendekatan dengan beberapa karakteristik berbeda yang menurut saya berharga:

  • memungkinkan> dan <perbandingan berdasarkan urutan enum, bukan urutan leksikal
  • dapat menangani item berdasarkan nama, properti atau indeks: xa, x ['a'] atau x [0]
  • mendukung operasi pemotongan seperti [:] atau [-1]

dan yang paling penting mencegah perbandingan antara enum dari berbagai jenis !

Berdasarkan erat pada http://code.activestate.com/recipes/413486-first-class-enums-in-python .

Banyak dokumen yang disertakan di sini untuk menggambarkan apa yang berbeda dari pendekatan ini.

def enum(*names):
    """
SYNOPSIS
    Well-behaved enumerated type, easier than creating custom classes

DESCRIPTION
    Create a custom type that implements an enumeration.  Similar in concept
    to a C enum but with some additional capabilities and protections.  See
    http://code.activestate.com/recipes/413486-first-class-enums-in-python/.

PARAMETERS
    names       Ordered list of names.  The order in which names are given
                will be the sort order in the enum type.  Duplicate names
                are not allowed.  Unicode names are mapped to ASCII.

RETURNS
    Object of type enum, with the input names and the enumerated values.

EXAMPLES
    >>> letters = enum('a','e','i','o','u','b','c','y','z')
    >>> letters.a < letters.e
    True

    ## index by property
    >>> letters.a
    a

    ## index by position
    >>> letters[0]
    a

    ## index by name, helpful for bridging string inputs to enum
    >>> letters['a']
    a

    ## sorting by order in the enum() create, not character value
    >>> letters.u < letters.b
    True

    ## normal slicing operations available
    >>> letters[-1]
    z

    ## error since there are not 100 items in enum
    >>> letters[99]
    Traceback (most recent call last):
        ...
    IndexError: tuple index out of range

    ## error since name does not exist in enum
    >>> letters['ggg']
    Traceback (most recent call last):
        ...
    ValueError: tuple.index(x): x not in tuple

    ## enums must be named using valid Python identifiers
    >>> numbers = enum(1,2,3,4)
    Traceback (most recent call last):
        ...
    AssertionError: Enum values must be string or unicode

    >>> a = enum('-a','-b')
    Traceback (most recent call last):
        ...
    TypeError: Error when calling the metaclass bases
        __slots__ must be identifiers

    ## create another enum
    >>> tags = enum('a','b','c')
    >>> tags.a
    a
    >>> letters.a
    a

    ## can't compare values from different enums
    >>> letters.a == tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    >>> letters.a < tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    ## can't update enum after create
    >>> letters.a = 'x'
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'a' is read-only

    ## can't update enum after create
    >>> del letters.u
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'u' is read-only

    ## can't have non-unique enum values
    >>> x = enum('a','b','c','a')
    Traceback (most recent call last):
        ...
    AssertionError: Enums must not repeat values

    ## can't have zero enum values
    >>> x = enum()
    Traceback (most recent call last):
        ...
    AssertionError: Empty enums are not supported

    ## can't have enum values that look like special function names
    ## since these could collide and lead to non-obvious errors
    >>> x = enum('a','b','c','__cmp__')
    Traceback (most recent call last):
        ...
    AssertionError: Enum values beginning with __ are not supported

LIMITATIONS
    Enum values of unicode type are not preserved, mapped to ASCII instead.

    """
    ## must have at least one enum value
    assert names, 'Empty enums are not supported'
    ## enum values must be strings
    assert len([i for i in names if not isinstance(i, types.StringTypes) and not \
        isinstance(i, unicode)]) == 0, 'Enum values must be string or unicode'
    ## enum values must not collide with special function names
    assert len([i for i in names if i.startswith("__")]) == 0,\
        'Enum values beginning with __ are not supported'
    ## each enum value must be unique from all others
    assert names == uniquify(names), 'Enums must not repeat values'

    class EnumClass(object):
        """ See parent function for explanation """

        __slots__ = names

        def __iter__(self):
            return iter(constants)

        def __len__(self):
            return len(constants)

        def __getitem__(self, i):
            ## this makes xx['name'] possible
            if isinstance(i, types.StringTypes):
                i = names.index(i)
            ## handles the more normal xx[0]
            return constants[i]

        def __repr__(self):
            return 'enum' + str(names)

        def __str__(self):
            return 'enum ' + str(constants)

        def index(self, i):
            return names.index(i)

    class EnumValue(object):
        """ See parent function for explanation """

        __slots__ = ('__value')

        def __init__(self, value):
            self.__value = value

        value = property(lambda self: self.__value)

        enumtype = property(lambda self: enumtype)

        def __hash__(self):
            return hash(self.__value)

        def __cmp__(self, other):
            assert self.enumtype is other.enumtype, 'Only values from the same enum are comparable'
            return cmp(self.value, other.value)

        def __invert__(self):
            return constants[maximum - self.value]

        def __nonzero__(self):
            ## return bool(self.value)
            ## Original code led to bool(x[0])==False, not correct
            return True

        def __repr__(self):
            return str(names[self.value])

    maximum = len(names) - 1
    constants = [None] * len(names)
    for i, each in enumerate(names):
        val = EnumValue(i)
        setattr(EnumClass, each, val)
        constants[i] = val
    constants = tuple(constants)
    enumtype = EnumClass()
    return enumtype
Chris Johnson
sumber
3

Berikut ini varian dari solusi Alec Thomas :

def enum(*args, **kwargs):
    return type('Enum', (), dict((y, x) for x, y in enumerate(args), **kwargs)) 

x = enum('POOH', 'TIGGER', 'EEYORE', 'ROO', 'PIGLET', 'RABBIT', 'OWL')
assert x.POOH == 0
assert x.TIGGER == 1
Roy Hyunjin Han
sumber
3

Solusi ini adalah cara sederhana untuk mendapatkan kelas untuk enumerasi yang didefinisikan sebagai daftar (tidak ada lagi tugas bilangan bulat yang mengganggu):

enumeration.py:

import new

def create(class_name, names):
    return new.classobj(
        class_name, (object,), dict((y, x) for x, y in enumerate(names))
    )

contoh.py:

import enumeration

Colors = enumeration.create('Colors', (
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
))
Michael Truog
sumber
2
Ini adalah cara yang sangat kuno untuk membuat kelas. Mengapa tidak menggunakan type(class_name, (object,), dict(...))saja saja?
terminus
3

Sementara proposal enum asli, PEP 354 , ditolak tahun lalu, itu terus muncul kembali. Beberapa jenis enum dimaksudkan untuk ditambahkan ke 3.2, tapi itu didorong kembali ke 3.3 dan kemudian dilupakan. Dan sekarang ada PEP 435 yang dimaksudkan untuk dimasukkan dalam Python 3.4. Referensi implementasi PEP 435 adalah flufl.enum.

Pada April 2013, tampaknya ada konsensus umum bahwa sesuatu harus ditambahkan ke perpustakaan standar di 3.4 — selama orang-orang dapat sepakat tentang apa "seharusnya" itu. Itu bagian yang sulit. Lihat utas mulai di sini dan di sini , dan setengah lusin utas lainnya di awal 2013.

Sementara itu, setiap kali ini muncul, banyak desain dan implementasi baru muncul di PyPI, ActiveState, dll., Jadi jika Anda tidak menyukai desain FLUFL, coba pencarian PyPI .

abarnert
sumber
3

Gunakan yang berikut ini.

TYPE = {'EAN13':   u'EAN-13',
        'CODE39':  u'Code 39',
        'CODE128': u'Code 128',
        'i25':     u'Interleaved 2 of 5',}

>>> TYPE.items()
[('EAN13', u'EAN-13'), ('i25', u'Interleaved 2 of 5'), ('CODE39', u'Code 39'), ('CODE128', u'Code 128')]
>>> TYPE.keys()
['EAN13', 'i25', 'CODE39', 'CODE128']
>>> TYPE.values()
[u'EAN-13', u'Interleaved 2 of 5', u'Code 39', u'Code 128']

Saya menggunakan itu untuk pilihan model Django , dan itu terlihat sangat pythonic. Ini sebenarnya bukan Enum, tetapi ia berhasil.

Natim
sumber