Câu hỏi Làm thế nào tôi có thể đại diện cho một 'Enum' trong Python?


Tôi chủ yếu là một 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 cho tương đương với một Enum trong Python?


1146


gốc




Các câu trả lời:


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

Để biết thêm các kỹ thuật Enum nâng cao, hãy thử thư viện aenum (2.7, 3.3+, cùng tác giả như enum34. Mã không hoàn toàn tương thích giữa py2 và py3, ví dụ: có thể 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

Đang 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 để hoàn thành enums 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

Hỗ trợ chuyển đổi các giá trị về tên có thể được thêm vào 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 đè bất cứ điều gì với tên đó, nhưng nó rất hữu ích cho việc hiển thị enums của bạn trong đầ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'

2320



+1 cho cách tiếp cận không có tiểu thuyết để sử dụng type()  docs.python.org/library/functions.html#type - Zachary Young
Giải pháp khéo léo! Tôi tự hỏi nếu / làm thế nào nó có thể được thực hiện để in tên của các định danh khi gỡ lỗi? Ví dụ, có print "The value is %r" % Numbers.ONE đầu ra The value is ONE thay vì The value is 1... - Pascal Bourque
Lưu ý rằng điều này không thực sự tương tự như một enum C, trong đó không thể có ý nghĩa có các biến / đối tượng của một loại trả về bởi enum. - Marcin
Ồ, tôi nhận thấy một sự cải thiện nhỏ có thể được thực hiện. zip(x, range(len(x)) là một mùi mã - nó có thể được thay thế bằng cách liệt kê để có được enums = dict((x, i) for i, x in enumerate(sequential)), **named) mà không xây dựng tất cả những danh sách mà zip / phạm vi làm. - Tom Swirly
Vấn đề với enum gói là loại enum của nó không tồn tại ngâm-unpickling. Tạo một enumaration Foo = Enum("foo", "bar"), sau đó so sánh pickle.loads(pickle.dumps(Foo.foo)) == Foo.foo; kết quả là False. Nó đau khi bạn có một chương trình đa xử lý, bởi vì các giá trị dường như bằng nhau dừng qua kiểm tra bình đẳng. - sastanin


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 đã nhìn thấy một số ví dụ phức tạp khủng khiếp trên mạng), một cái gì đó như thế này ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Trong Python 3.4 (PEP 435), bạn có thể làm Enum 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 biệt với các số nguyên, và chúng bao gồm một name và một 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)

Enum triể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 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

721



Không, đó là biến lớp. - Georg Schölly
Python là động theo mặc định. Không có lý do hợp lệ để thực thi tính an toàn biên dịch trong một ngôn ngữ như Python, đặc biệt là khi không có ngôn ngữ nào. 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ẫu 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ông cụ bạn đang sử dụng. - Alexandru Nedelcu
@Longpoke nếu bạn có 100 giá trị, sau đó bạn chắc chắn đang làm điều gì đó sai;) Tôi thích số liên quan đến enums của tôi ... họ rất dễ viết (vs strings), có thể dễ dàng tồn tại trong cơ sở dữ liệu và tương thích với C / C ++ enum, mà làm cho dễ dàng hơn marshaling. - Alexandru Nedelcu
Tôi sử dụng điều này, với những con số được thay thế bằng object(). - Tobu
Bản gốc PEP354 không còn bị từ chối, nhưng bây giờ được đánh dấu thay thế. PEP435 bổ sung Enum chuẩn cho Python 3.4. Xem python.org/dev/peps/pep-0435 - Peter Hansen


Dưới đây là một triển khai:

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)

298



Xuất sắc. Đ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) để 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
@shahjapan: Thú vị, nhưng tương đối chậm: một thử nghiệm đượ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, để so sánh với các hằng số này chậm hơn nếu, các số nguyên được cho phép dưới dạng các giá trị. - Eric Lebigot
@shahjapan: Tôi cho rằng giải pháp này không dễ đọc 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 Lebigot
Tôi đã thử sử dụng setattr() chức năng bên trong __init__() phương pháp thay vì overidding __getattr__() phương pháp. Tôi giả định này được cho là làm việc theo cùng một cách: class Enum (object): def __init __ (self, enum_string_list): nếu kiểu (enum_string_list) == list: cho enum_string trong enum_string_list: setattr (self, enum_string, enum_string) else: raise AttributeError - Harshith J.V.
@ AndréTerra: làm thế nào để bạn kiểm tra cho các thành viên thiết lập trong một try-except khối? - bgusach


Nếu bạn cần giá trị số, dưới đâ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, nó 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 nhớ lãng phí bộ nhớ và không thể đếm:

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

183





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ừ giả mạo  enum.

Enum đơn giản:

Nếu bạn cần enum như một danh sách tên xác định khác nhau mặt hàng, giải pháp của Mark Harrison (ở trên) là rất tốt:

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

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

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

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

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 vùng chứa và tên mục:

stype = Stationery.Pen

Enum phức tạp:

Đối với danh sách dài của enum hoặc sử dụng phức tạp hơn của enum, 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 cho Mô phỏng Enumerations trong Python xuất bản trong Sách dạy nấu ăn Python. Một phiên bản trực tuyến có sẵn đây.

Thêm thông tin:

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


119



với range bạn có thể bỏ qua đối số đầu tiên nếu nó là 0 - ToonAlfrink
Một enum giả khác 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_enum có thể được sử dụng để 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 kết quả của str.split trong một hàm ném một ngoại lệ nếu có bất kỳ bản sao nào. - Ana Nimbus


Mẫu enum an toàn được sử dụng trong Java pre-JDK 5 có một số lợi thế. Giống như 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ớp là các giá trị enum; tuy nhiên, enum các giá trị là các thể hiện của lớp chứ không phải là các số nguyên nhỏ. Điều này có lợi thế mà giá trị enum của bạn không vô tình so sánh bằng nhau với số nguyên nhỏ, bạn có thể kiểm soát cách chúng được in, thêm tùy ý các phương thức 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ới đây luồng trên python-dev chỉ ra có một vài thư viện enum trong tự nhiên, bao gồm:


76



Tôi nghĩ rằng đây là một cách tiếp cận rất xấu. Animal.DOG = Động vật ("chó") Animal.DOG2 = Động vật ("chó") khẳng định Animal.DOG == Animal.DOG2 thất bại ... - Confusion
@Confusion Người dùng không được phép gọi hàm tạo, thực tế là thậm chí có 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 người đã từng sử dụng mã của mình để tạo ra các giá trị đếm mới không có ý nghĩa và mã thoát sẽ không "Làm điều đúng đắn". Tất nhiên điều đó không ngăn cản bạn thực hiện Animal.from_name ("chó") -> Animal.DOG. - Aaron Maenpaa
"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 các 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 các số nguyên tại một số điểm. - ibz
@Aaaron Maenpaa. chính xác. Nó vẫn là một cách bị hỏng và quá phức tạp để làm điều đó. - aaronasterling
@AaronMcSmooth Điều đó thực sự phụ thuộc vào việc bạn đang đến từ quan điểm C của "Enums chỉ là tên cho một vài ints" hay phương pháp 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 enums trong Java 1,5 là, và kiểu an toàn enum nào đang được sử dụng). Cá nhân, tôi không thích báo cáo chuyển đổi vì vậy tôi nghiêng về phía giá trị enum là đối tượng thực tế. - Aaron Maenpaa


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

class Enum(tuple): __getattr__ = tuple.index

Làm thế nào để sử dụng nó (tra cứu ngược, đảo ngược, các giá trị, các mục, vv)

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

56



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 (có, máy chủ cũ cũ) tuples đã không chỉ mục. Tôi đã giải quyết thay thế bằng danh sách. - Massimo
Tôi đã thử nó trong một sổ ghi chép của 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, mà là getattr định nghĩa trên dòng thứ hai (thụt vào) sẽ được chấp nhận. - user5920660