Làm cách nào tôi có thể biểu thị một 'Enum' trong Python?


1143

Tôi chủ yếu là nhà phát triển C #, nhưng tôi hiện đang làm việc trên một dự án bằng Python.

Làm thế nào tôi có thể đại diện tương đương với một Enum trong Python?

Câu trả lời:


2688

Enums đã được thêm vào Python 3.4 như được mô tả trong PEP 435 . Nó cũng đã được nhập vào 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 và 2.4 trên pypi.

Để biết các kỹ thuật Enum nâng cao hơn, hãy thử thư viện aenum (2.7, 3.3+, cùng tác giả với enum34. Mã không tương thích hoàn hảo giữa py2 và py3, ví dụ: bạn sẽ cần __order__trong python 2 ).

  • Để sử dụng enum34, làm$ pip install enum34
  • Để sử dụng aenum, làm$ pip install aenum

Cài đặt enum(không có số) sẽ cài đặt một phiên bản hoàn toàn khác và không tương thích.


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)

hoặc tương đương:

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

Trong các phiên bản trước, một cách để thực hiện enum là:

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

được sử dụng như vậy:

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

Bạn cũng có thể dễ dàng hỗ trợ liệt kê tự động với một cái gì đó như thế này:

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

và được sử dụng như vậy:

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

Có thể thêm hỗ trợ để chuyển đổi các giá trị thành tên theo cách này:

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)

Điều này ghi đè lên bất cứ thứ gì có tên đó, nhưng nó rất hữu ích để hiển thị enum của bạn ở đầu ra. Nó sẽ ném KeyError nếu ánh xạ ngược không tồn tại. Với ví dụ đầu tiên:

>>> Numbers.reverse_mapping['three']
'THREE'

1
Tôi không thể hiểu được, tại sao họ lại vượt qua kwargs (** có tên) trong phương thức enum (* tuần tự, ** có tên)? Hãy giải thích. Không có kwargs nó cũng sẽ hoạt động. Tôi đã kiểm tra nó.
Seenu S

Sẽ thật tuyệt khi cập nhật chức năng Python 2 để tương thích với API chức năng của Python 3 (tên, giá trị)
bscan

Var kwargs ( **named) trong hàm enum cho các phiên bản cũ hơn là để hỗ trợ các giá trị tùy chỉnh:enum("blue", "red", "green", black=0)
Éric Araujo

823

Trước PEP 435, Python không có tương đương nhưng bạn có thể tự thực hiện.

Bản thân tôi, tôi thích giữ nó đơn giản (tôi đã thấy một số ví dụ phức tạp khủng khiếp trên mạng), đại loại như thế này ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Trong Python 3.4 ( PEP 435 ), bạn có thể biến Enum thành lớp cơ sở. Điều này giúp bạn có thêm một chút chức năng bổ sung, được mô tả trong PEP. Ví dụ, các thành viên enum khác với các số nguyên và chúng bao gồm a namevà 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"

Nếu bạn không muốn nhập các giá trị, hãy sử dụng phím tắt sau:

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

Enumtriển khai có thể được chuyển đổi thành danh sách và có thể lặp lại . Thứ tự của các thành viên là thứ tự khai báo và không liên quan gì đến các giá trị của chúng. Ví dụ:

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

51
Không, đó là một biến lớp.
Georg Schölly

246
Python là động theo mặc định. Không có lý do hợp lệ để thực thi an toàn thời gian biên dịch bằng ngôn ngữ như Python, đặc biệt là khi không có. Và một điều nữa ... một mô hình tốt chỉ tốt trong bối cảnh mà nó được tạo ra. Một mô hình tốt cũng có thể được thay thế hoặc hoàn toàn vô dụng, tùy thuộc vào các công cụ bạn đang sử dụng.
Alexandru Nedelcu

20
@Longpoke nếu bạn có 100 giá trị, thì bạn chắc chắn đã làm sai điều gì đó enum C / C ++, giúp cho việc sắp xếp dễ dàng hơn.
Alexandru Nedelcu

50
Tôi sử dụng cái này, với những con số được thay thế bởi object().
Tobu

9
PEP354 ban đầu không còn bị từ chối đơn thuần, mà giờ được đánh dấu thay thế. PEP435 thêm Enum tiêu chuẩn cho Python 3.4. Xem python.org/dev/peps/pep-0435
Peter Hansen

322

Đây là một cách thực hiện:

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

Đây là cách sử dụng của nó:

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

print(Animals.DOG)

51
Thông minh. Điều này có thể được cải thiện hơn nữa bằng cách ghi đè __setattr__(self, name, value)và có thể __delattr__(self, name)vì vậy nếu bạn vô tình viết Animals.DOG = CAT, nó sẽ không âm thầm thành công.
Joonas Pulakka

15
@shahjapan: Thú vị, nhưng tương đối chậm: một bài kiểm tra được thực hiện cho mỗi lần truy cập như Animals.DOG; Ngoài ra, các giá trị của các hằng số là các chuỗi, do đó việc so sánh với các hằng số này chậm hơn so với nếu các số nguyên được cho phép là các giá trị.
Eric O Lebigot

3
@shahjapan: Tôi cho rằng giải pháp này không rõ ràng như các giải pháp ngắn hơn của Alexandru hoặc Mark chẳng hạn. Đó là một giải pháp thú vị, mặc dù. :)
Eric O Lebigot

Tôi đã thử sử dụng setattr()hàm bên trong __init__()phương thức thay vì __getattr__()phương thức overidding . Tôi giả sử điều này được cho là hoạt động theo cùng một cách: class Enum (object): def __init __ (self, enum_opes_list): if type (enum_opes_list) == list: for enum_opes in enum_opes_list: setattr (self, enum_opes, enum_opes AttributionError
Harshith JV

8
@ AndréTerra: làm thế nào để bạn kiểm tra thiết lập thành viên trong một try-exceptkhối?
bgusach

210

Nếu bạn cần các giá trị số, đây là cách nhanh nhất:

dog, cat, rabbit = range(3)

Trong Python 3.x, bạn cũng có thể thêm một trình giữ chỗ được gắn dấu sao ở cuối, điều này sẽ hấp thụ tất cả các giá trị còn lại của phạm vi trong trường hợp bạn không lãng phí bộ nhớ và không thể đếm:

dog, cat, rabbit, horse, *_ = range(100)

1
Nhưng điều này có thể mất nhiều bộ nhớ hơn!
MJ

Tôi không thấy điểm của trình giữ chỗ được gắn dấu sao cho rằng Python sẽ kiểm tra số lượng giá trị để giải nén (vì vậy nó sẽ thực hiện việc đếm cho bạn).
Gabriel Devillers

@GabrielDevillers, tôi nghĩ Python sẽ đưa ra một ngoại lệ nếu có sự không phù hợp về số lượng phần tử trong bộ dữ liệu cần gán.
Đánh dấu Harrison

1
Thật vậy, nó thực hiện trong thử nghiệm của tôi (Python2,3) nhưng điều đó có nghĩa là bất kỳ lỗi đếm nào từ lập trình viên sẽ bị bắt trong thử nghiệm đầu tiên (với một thông báo đưa ra số đếm chính xác).
Gabriel Devillers

1
Tôi không thể đếm được. Giữ chỗ được gắn sao cũng có thể sửa chữa tài chính của tôi?
javadba

131

Giải pháp tốt nhất cho bạn sẽ phụ thuộc vào những gì bạn yêu cầu từ hàng giả của bạn enum.

Enum đơn giản:

Nếu bạn enumchỉ cần một danh sách các tên xác định các mục khác nhau , thì giải pháp của Mark Harrison (ở trên) là tuyệt vời:

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

Sử dụng rangecũng cho phép bạn đặt bất kỳ giá trị bắt đầu :

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

Ngoài việc trên, nếu bạn cũng yêu cầu các mặt hàng thuộc về một thùng chứa của một số loại, sau đó nhúng chúng trong một lớp học:

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

Để sử dụng mục enum, bây giờ bạn sẽ cần sử dụng tên container và tên mục:

stype = Stationery.Pen

Enum phức tạp:

Đối với danh sách dài enum hoặc sử dụng enum phức tạp hơn, các giải pháp này sẽ không đủ. Bạn có thể tìm đến công thức của Will Ware để mô phỏng liệt kê trong Python được xuất bản trong Python Cookbook . Một phiên bản trực tuyến có sẵn ở đây .

Thêm thông tin:

PEP 354: Các liệt kê trong Python có các chi tiết thú vị về một đề xuất cho enum trong Python và tại sao nó bị từ chối.


7
với rangebạn có thể bỏ qua đối số đầu tiên nếu nó là 0
ToonAlfrink

Một enum giả phù hợp với một số mục đích là my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Sau đó, my_enumcó thể được sử dụng trong tra cứu, ví dụ, my_enum['Item0']có thể là một chỉ mục thành một chuỗi. Bạn có thể muốn bọc kết quả của str.splitmột hàm ném ngoại lệ nếu có bất kỳ bản sao nào.
Ana Nimbus

Đẹp! Đối với Cờ bạn có thểFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx

Đây là câu trả lời hay nhất
Yuriy Pozniak

78

Mẫu enum an toàn được sử dụng trong Java trước JDK 5 có một số lợi thế. Giống như trong câu trả lời của Alexandru, bạn tạo ra các trường cấp lớp và cấp độ là các giá trị enum; tuy nhiên, các giá trị enum là các thể hiện của lớp thay vì các số nguyên nhỏ. Điều này có lợi thế là các giá trị enum của bạn không vô tình so sánh bằng các số nguyên nhỏ, bạn có thể kiểm soát cách chúng được in, thêm các phương thức tùy ý nếu điều đó hữu ích và đưa ra các xác nhận bằng cách sử dụng 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

Một chủ đề gần đây trên python-dev chỉ ra rằng có một vài thư viện enum trong tự nhiên, bao gồm:


16
Tôi nghĩ rằng đây là một cách tiếp cận rất xấu. Animal.DOG = Animal ("dog") Animal.DOG2 = Animal ("dog") khẳng định Animal.DOG == Animal.DOG2 thất bại ...
Nhầm lẫn

11
@Confusion Người dùng không cần phải gọi hàm tạo, thực tế là thậm chí một hàm tạo là một chi tiết triển khai và bạn phải liên lạc với ai đã từng sử dụng mã của mình để tạo ra các giá trị liệt kê mới vô nghĩa và việc thoát mã sẽ không "Làm điều đúng đắn". Tất nhiên, điều đó không ngăn bạn triển khai Animal.from_name ("chó") -> Animal.DOG.
Aaron Maenpaa

13
"lợi thế mà giá trị enum của bạn không vô tình so sánh bằng số nguyên nhỏ" Lợi thế trong việc này là gì? Có gì sai khi so sánh enum của bạn với số nguyên? Đặc biệt nếu bạn lưu trữ enum trong cơ sở dữ liệu, bạn thường muốn nó được lưu trữ dưới dạng số nguyên, vì vậy bạn sẽ phải so sánh nó với số nguyên tại một số điểm.
ibz

3
@Aaaron Maenpaa. chính xác. Đó vẫn là một cách hỏng và quá phức tạp để làm điều đó.
aaronasterling

4
@AaronMcSmooth Điều đó thực sự phụ thuộc vào việc bạn đến từ góc độ C của "Enums chỉ là tên của một vài số nguyên" hay cách tiếp cận hướng đối tượng hơn trong đó các giá trị enum là các đối tượng thực tế và có các phương thức (đó là cách enum trong Java 1,5 là, và kiểu mẫu enum an toàn đã được sử dụng). Cá nhân, tôi không thích các câu lệnh chuyển đổi vì vậy tôi nghiêng về các giá trị enum là các đối tượng thực tế.
Aaron Maenpaa

61

Một lớp Enum có thể là một lớp lót.

class Enum(tuple): __getattr__ = tuple.index

Cách sử dụng (tra cứu tiến và lùi, khóa, giá trị, vật phẩm, v.v.)

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

Tôi nghĩ rằng đó là giải pháp đơn giản nhất kết thúc thanh lịch nhất. Trong python 2.4 (vâng, máy chủ cũ) không có chỉ mục. Tôi đã giải quyết thay thế bằng danh sách.
Massimo

Tôi đã thử điều này trong một máy tính xách tay Jupyter và phát hiện ra rằng nó sẽ không hoạt động như một định nghĩa một dòng, nhưng việc đưa định nghĩa getattr lên một dòng thứ hai (thụt vào) sẽ được chấp nhận.
dùng5920660

Giải pháp này cho tôi sử dụng intừ khóa để tìm kiếm các thành viên gọn gàng. Ví dụ sử dụng:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini

51

Vì vậy, tôi đồng ý. Chúng ta đừng thực thi loại an toàn trong Python, nhưng tôi muốn bảo vệ bản thân khỏi những sai lầm ngớ ngẩn. Vậy chúng ta nghĩ gì về điều này?

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

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

Nó giữ cho tôi khỏi xung đột giá trị trong việc xác định enum của tôi.

>>> Animal.Cat
2

Có một lợi thế tiện dụng khác: tra cứu ngược rất nhanh:

def name_of(self, i):
    return self.values[i]

Tôi thích điều này, nhưng bạn cũng có thể khóa các giá trị để đạt hiệu quả với một tuple? Tôi đã chơi xung quanh nó và đưa ra một phiên bản đặt self.values ​​từ args trong init . Nó là tốt đẹp để có thể tuyên bố Animal = Enum('horse', 'dog', 'cat'). Tôi cũng bắt gặp ValueError trong getattr trong trường hợp có một mục bị thiếu trong self.values ​​- có vẻ tốt hơn để tăng AttributionError bằng chuỗi tên được cung cấp thay thế. Tôi không thể làm cho siêu dữ liệu hoạt động trong Python 2.7 dựa trên kiến ​​thức hạn chế của mình trong khu vực đó, nhưng lớp Enum tùy chỉnh của tôi hoạt động tốt với các phương thức cá thể thẳng.
trojjer

49

Python không có sẵn tích hợp tương đương enumvà các câu trả lời khác có ý tưởng để tự thực hiện (bạn cũng có thể quan tâm đến phiên bản cao nhất trong sách dạy nấu ăn Python).

Tuy nhiên, trong các tình huống enumsẽ được gọi bằng C, tôi thường chỉ sử dụng các chuỗi đơn giản : do cách thức thực hiện các đối tượng / thuộc tính, (C) Python được tối ưu hóa để hoạt động rất nhanh với các chuỗi ngắn, vì vậy sẽ không 'Thực sự là bất kỳ lợi ích hiệu suất để sử dụng số nguyên. Để bảo vệ chống lại lỗi chính tả / giá trị không hợp lệ, bạn có thể chèn séc vào các vị trí đã chọn.

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

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

(Một nhược điểm so với việc sử dụng một lớp là bạn mất lợi ích của tự động hoàn thành)


2
Tôi thích giải pháp này. Tôi thích sử dụng các loại tích hợp nếu có thể.
Seun Osewa

Phiên bản đó không thực sự vượt trội. Nó chỉ có rất nhiều mã thử nghiệm được cung cấp
Casebash

1
Trên thực tế, phiên bản "chính xác" nằm trong phần bình luận và phức tạp hơn nhiều - phiên bản chính có một lỗi nhỏ.
Casebash

39

Vào ngày 2013-05-10, Guido đã đồng ý chấp nhận PEP 435 vào thư viện chuẩn Python 3.4. Điều này có nghĩa là Python cuối cùng đã hỗ trợ dựng sẵn cho bảng liệt kê!

Có một backport có sẵn cho Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 và 2.4. Đó là trên Pypi như enum34 .

Tờ khai:

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

Đại diện:

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

Lặp lại:

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

Truy cập theo chương trình:

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

Để biết thêm thông tin, tham khảo các đề xuất . Tài liệu chính thức có thể sẽ theo sau.


33

Tôi thích định nghĩa enums trong Python như vậy:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Nó chống lỗi nhiều hơn so với sử dụng số nguyên vì bạn không phải lo lắng về việc đảm bảo rằng số nguyên là duy nhất (ví dụ: nếu bạn nói Dog = 1 và Cat = 1 bạn sẽ bị lừa).

Nó chống lỗi nhiều hơn so với sử dụng chuỗi vì bạn không phải lo lắng về lỗi chính tả (ví dụ: x == "catt" không thành công, nhưng x == Animal.Catt là ngoại lệ thời gian chạy).


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

Sử dụng nó như thế này:

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

nếu bạn chỉ muốn các biểu tượng độc đáo và không quan tâm đến các giá trị, hãy thay thế dòng này:

__metaclass__ = M_add_class_attribs(enumerate(names))

Với cái này:

__metaclass__ = M_add_class_attribs((object(), name) for name in names)

11
IMHO sẽ sạch hơn nếu bạn đổi enum(names)thành enum(*names)- sau đó bạn có thể bỏ dấu ngoặc đơn bổ sung khi gọi nó.
Chris Lutz

Tôi thích cách tiếp cận này. Tôi thực sự đã thay đổi nó để đặt giá trị thuộc tính thành cùng một chuỗi với tên, có thuộc tính đẹp mà Animal.DOG == 'DOG', để chúng tự động xâu chuỗi cho bạn. (Giúp rất nhiều cho việc in ra đầu ra gỡ lỗi.)
Ted Mielczarek

23

Từ Python 3.4 sẽ có hỗ trợ chính thức cho enums. Bạn có thể tìm thấy tài liệu và ví dụ ở đây trên trang tài liệu Python 3.4 .

Các liệt kê được tạo bằng cú pháp lớp, giúp chúng dễ đọc và viết. Một phương pháp tạo thay thế được mô tả trong API chức năng. Để xác định một phép liệt kê, phân lớp Enum như sau:

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

Quay lại cổng bây giờ cũng được hỗ trợ. Đây là con đường để đi.
srock

22

Hmmm ... Tôi cho rằng thứ gần gũi nhất với enum sẽ là một cuốn từ điển, được định nghĩa như thế này:

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

hoặc là

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

Sau đó, bạn có thể sử dụng tên tượng trưng cho các hằng số như thế này:

mymonth = months['January']

Có các tùy chọn khác, như danh sách các bộ dữ liệu hoặc bộ dữ liệu, nhưng từ điển là lựa chọn duy nhất cung cấp cho bạn cách "biểu tượng" (chuỗi không đổi) để truy cập giá trị.

Chỉnh sửa: Tôi cũng thích câu trả lời của Alexandru!


Và hầu hết tất cả bạn có thể dễ dàng lặp lại trên một từ điển nếu bạn cần truy cập các giá trị của nó giống như bạn cần các giá trị chuỗi của nó để xuất hiện dưới dạng các mục hộp tổ hợp. Vì vậy, sử dụng một từ điển để thay thế cho liệt kê thay thế.
LEMUEL ADane

22

Một cách khác, rất đơn giản, thực hiện một enum trong Python, sử dụng namedtuple:

from collections import namedtuple

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

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

Hay cách khác,

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

Giống như phương thức trên các lớp con đó set, điều này cho phép:

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

Nhưng có tính linh hoạt hơn vì nó có thể có các khóa và giá trị khác nhau. Điều này cho phép

MyEnum.FOO < MyEnum.BAR

để hoạt động như mong đợi nếu bạn sử dụng phiên bản điền vào các giá trị số liên tiếp.


20

Những gì tôi sử dụng:

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

Cách sử dụng:

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

Vì vậy, điều này cung cấp cho bạn các hằng số nguyên như state.PUBOUNDED và hai bộ dữ liệu để sử dụng làm lựa chọn trong các mô hình Django.


17

davidg khuyên bạn nên sử dụng dicts. Tôi sẽ tiến thêm một bước và sử dụng các bộ:

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

Bây giờ bạn có thể kiểm tra xem một giá trị có khớp với một trong các giá trị trong tập hợp như sau không:

if m in months:

Tuy nhiên, như dF, tôi thường chỉ sử dụng các hằng chuỗi thay cho enum.


vâng, tốt hơn nhiều nếu bạn kế thừa tập hợp và cung cấp phương thức getattr !
shahjapan

17

Giữ cho nó đơn giản:

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

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

Sau đó:

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

16

Đây là cái tốt nhất tôi từng thấy: "First Class Enums in Python"

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

Nó cung cấp cho bạn một lớp và lớp chứa tất cả các enum. Các enum có thể được so sánh với nhau, nhưng không có bất kỳ giá trị cụ thể nào; bạn không thể sử dụng chúng làm giá trị nguyên. . .) Mỗi ​​enum là một giá trị duy nhất. Bạn có thể in enum, bạn có thể lặp lại chúng, bạn có thể kiểm tra xem giá trị enum có "trong" enum không. Nó khá hoàn chỉnh và bóng bẩy.

Chỉnh sửa (cfi): Liên kết trên không tương thích với Python 3. Đây là cổng enum.py của tôi với 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)

Công thức này đã được sử dụng làm cơ sở cho PEP, đã bị từ chối. python.org/dev/peps/pep-0354 Một tiện ích mở rộng mà tôi thích: giá trị enum nên có một biến thành viên cho phép bạn lấy giá trị nguyên bên trong. Không thể nhầm enum thành một số nguyên do nhầm lẫn, vì vậy .__int__()phương thức sẽ đưa ra một ngoại lệ cho một enum; nhưng nên có một cách để có được giá trị. Và có thể đặt các giá trị nguyên cụ thể theo thời gian xác định lớp, vì vậy bạn có thể sử dụng một enum cho những thứ như các hằng số trong statmô-đun.
steveha

14

Tôi đã có dịp cần một lớp Enum, với mục đích giải mã một định dạng tệp nhị phân. Các tính năng tôi tình cờ muốn là định nghĩa enum súc tích, khả năng tự do tạo ra các thể hiện của enum theo giá trị nguyên hoặc chuỗi và một ý nghĩa hữu ích repr. Đây là những gì tôi đã kết thúc với:

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

Một ví dụ hay thay đổi khi sử dụng nó:

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

Các tính năng chính:

  • str(), int()repr()tất cả các sản phẩm đầu ra hữu dụng nhất có thể, lần lượt tên của enumartion, giá trị số nguyên của nó, và một biểu Python đánh giá lại sao cho kiểu liệt kê.
  • Các giá trị liệt kê được trả về bởi hàm tạo được giới hạn đúng với các giá trị được xác định trước, không có giá trị enum ngẫu nhiên.
  • Giá trị được liệt kê là singletons; họ có thể được so sánh nghiêm ngặt vớiis

Tôi thực sự thích việc sử dụng một siêu lớp với siêu dữ liệu riêng của nó, để làm cho nó dễ dàng xác định enums. Điều còn thiếu ở đây là phương thức __contains__. Tôi muốn có thể kiểm tra xem một biến đã cho có phải là một phần của enum hay không - chủ yếu là vì tôi muốn các enum cho các giá trị cho phép của một tham số hàm.
xorsyst

Đây thực sự là một phiên bản được cắt xén một chút của phiên bản ban đầu tôi đã tạo (mà bạn có thể tìm thấy ở đây: enum_strict.py ) v trong đó xác định một __instancecheck__phương thức. Các lớp học không phải là tập hợp các thể hiện, vì vậy 1 in Fruitlà vô lý. Tuy nhiên, phiên bản được liên kết hỗ trợ isinstance(1, Fruit)sẽ chính xác hơn về mặt khái niệm các lớp và thể hiện.
SingleNegationElimination

Nhưng quên các lớp học và suy nghĩ về enum, thì sẽ có ý nghĩa khi nghĩ về chúng như một bộ sưu tập. Ví dụ: tôi có thể có một chế độ mở tệp (MODE.OPEN, MODE.WRITE, v.v.). Tôi muốn xác minh các tham số cho chức năng của mình: if mode trong MODE: đọc tốt hơn rất nhiều so với isintance (mode, Mode)
xorsyst

Tôi đã đưa ra một cái gì đó rất giống nhau, nó hỗ trợ nhiều hơn chỉ ints, và được ghi lại và thử nghiệm, trên GitHub: github.com/hmeine/named_constants
hans_meine

12

Tiêu chuẩn mới trong Python là PEP 435 , do đó, một lớp Enum sẽ có sẵn trong các phiên bản tương lai của Python:

>>> from enum import Enum

Tuy nhiên, để bắt đầu sử dụng ngay bây giờ, bạn có thể cài đặt thư viện gốc tạo động lực cho PEP:

$ pip install flufl.enum

Sau đó, bạn có thể sử dụng nó theo hướng dẫn trực tuyến của nó :

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

10
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Nếu bạn đặt tên cho nó, đó là vấn đề của bạn, nhưng nếu không tạo đối tượng thay vì giá trị cho phép bạn thực hiện việc này:

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

Khi sử dụng các triển khai khác được đặt ở đây (cũng như khi sử dụng các thể hiện được đặt tên trong ví dụ của tôi), bạn phải chắc chắn rằng bạn không bao giờ cố gắng so sánh các đối tượng từ các enum khác nhau. Đối với đây là một cạm bẫy có thể:

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

Rất tiếc!


9

Tôi thực sự thích giải pháp của Alec Thomas (http://stackoverflow.com/a/1695250):

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

Nó trông thanh lịch và sạch sẽ, nhưng nó chỉ là một chức năng tạo ra một lớp với các thuộc tính được chỉ định.

Với một chút sửa đổi cho chức năng, chúng ta có thể khiến nó hoạt động nhiều hơn một chút 'ghen tị':

LƯU Ý: Tôi đã tạo các ví dụ sau bằng cách cố gắng tái tạo hành vi của kiểu 'enums' của pygtk (như 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

Điều này tạo ra một enum dựa trên một loại xác định. Ngoài việc cấp quyền truy cập thuộc tính như hàm trước, nó hoạt động như bạn mong đợi một Enum đối với các loại. Nó cũng kế thừa lớp cơ sở.

Ví dụ: enums số nguyên:

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

Một điều thú vị khác có thể được thực hiện với phương pháp này là tùy chỉnh hành vi cụ thể bằng cách ghi đè các phương thức tích hợp:

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'

ý tưởng kiểu "cơ sở" này là gọn gàng :)
MestreLion

vâng, lưu ý rằng bạn cũng có thể làm điều này với Python 3,4 Enum mới: python.org/dev/peps/pep-0435/#other-derured-enumerations
bj0

7

Gói enum từ PyPI cung cấp một triển khai mạnh mẽ của enum . Một câu trả lời trước đó đã đề cập đến PEP 354; điều này đã bị từ chối nhưng đề xuất đã được thực hiện http://pypi.python.org/pypi/enum .

Cách sử dụng rất dễ dàng và thanh lịch:

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

5

Đề xuất của Alexandru về việc sử dụng hằng số lớp cho enums hoạt động khá tốt.

Tôi cũng muốn thêm một từ điển cho mỗi bộ hằng số để tra cứu một đại diện chuỗi có thể đọc được.

Điều này phục vụ hai mục đích: a) nó cung cấp một cách đơn giản để in đẹp enum của bạn và b) từ điển nhóm các hằng số để bạn có thể kiểm tra tư cách thành viên.

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

5

Đây là một cách tiếp cận với một số đặc điểm khác nhau mà tôi thấy có giá trị:

  • cho phép> và <so sánh dựa trên thứ tự trong enum, không phải thứ tự từ vựng
  • có thể giải quyết mục theo tên, thuộc tính hoặc chỉ mục: xa, x ['a'] hoặc x [0]
  • hỗ trợ các hoạt động cắt lát như [:] hoặc [-1]

và quan trọng nhất là ngăn chặn sự so sánh giữa các loại khác nhau !

Dựa trên http://code.activestate.com/recipes/413486-first- class-enums-in-python .

Nhiều tài liệu bao gồm ở đây để minh họa những gì khác nhau về phương pháp này.

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

3

Đây là một biến thể của giải pháp 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

3

Giải pháp này là một cách đơn giản để nhận một lớp cho phép liệt kê được định nghĩa là một danh sách (không có các phép gán số nguyên khó chịu hơn):

liệt kê:

import new

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

ví dụ:

import enumeration

Colors = enumeration.create('Colors', (
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
))

2
Đây là một cách thực sự lâu đời để tạo ra các lớp. Tại sao không chỉ đơn giản là sử dụng type(class_name, (object,), dict(...))thay thế?
bến cuối

3

Trong khi đề xuất enum ban đầu, PEP 354 , đã bị từ chối nhiều năm trước, nó vẫn tiếp tục trở lại. Một số loại enum đã được dự định thêm vào 3.2, nhưng nó đã bị đẩy lùi về 3.3 và sau đó bị lãng quên. Và bây giờ có PEP 435 dự định đưa vào Python 3.4. Việc thực hiện tham chiếu của PEP 435 là flufl.enum.

Kể từ tháng 4 năm 2013, dường như có một sự đồng thuận chung rằng một cái gì đó nên được thêm vào thư viện tiêu chuẩn trong 3,4, miễn là mọi người có thể đồng ý về "cái gì đó" nên là gì. Đó là phần khó khăn. Xem các chủ đề bắt đầu ở đâyở đây , và một nửa tá các chủ đề khác trong những tháng đầu năm 2013.

Trong khi đó, mỗi khi điều này xuất hiện, một loạt các thiết kế và triển khai mới xuất hiện trên PyPI, ActiveState, v.v., vì vậy nếu bạn không thích thiết kế FLUFL, hãy thử tìm kiếm PyPI .


3

Sử dụng như sau.

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

Tôi đã sử dụng nó cho các lựa chọn mô hình Django , và nó trông rất pythonic. Nó không thực sự là một Enum, nhưng nó thực hiện công việc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.