列舉 HOWTO

Enum 是一組繫結到唯一值的符號名稱。它們類似於全域性變數,但提供了更有用的 repr()、分組、型別安全以及其他一些功能。

當您有一個變數只能取一組有限的值時,它們最有用。例如,一週中的日子

>>> from enum import Enum
>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7

或者 RGB 原色

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3

正如您所看到的,建立一個 Enum 就像編寫一個繼承自 Enum 的類一樣簡單。

備註

列舉成員的大小寫

由於列舉用於表示常量,並且為了幫助避免 mixin 類方法/屬性與列舉名稱之間的名稱衝突問題,我們強烈建議對成員使用大寫名稱,並且在我們的示例中將使用這種風格。

根據列舉的性質,成員的值可能重要,也可能不重要,但無論哪種方式,該值都可以用於獲取相應的成員

>>> Weekday(3)
<Weekday.WEDNESDAY: 3>

如您所見,成員的 repr() 顯示列舉名稱、成員名稱和值。成員的 str() 僅顯示列舉名稱和成員名稱

>>> print(Weekday.THURSDAY)
Weekday.THURSDAY

列舉成員的 型別 是它所屬的列舉

>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True

列舉成員有一個屬性,其中只包含它們的 name

>>> print(Weekday.TUESDAY.name)
TUESDAY

同樣,它們還有一個用於其 value 的屬性

>>> Weekday.WEDNESDAY.value
3

與許多僅將列舉視為名稱/值對的語言不同,Python 列舉可以新增行為。例如,datetime.date 有兩個返回工作日的方法:weekday()isoweekday()。區別在於一個從 0-6 計數,另一個從 1-7 計數。我們可以向 Weekday 列舉新增一個方法,從 date 例項中提取日期並返回匹配的列舉成員,而不是自己跟蹤。

@classmethod
def from_date(cls, date):
    return cls(date.isoweekday())

完整的 Weekday 列舉現在看起來像這樣

>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...     #
...     @classmethod
...     def from_date(cls, date):
...         return cls(date.isoweekday())

現在我們可以知道今天是星期幾了!請看

>>> from datetime import date
>>> Weekday.from_date(date.today())
<Weekday.TUESDAY: 2>

當然,如果您在其他日期閱讀此內容,您將看到該日期。

如果我們的變數只需要一天,這個 Weekday 列舉很好,但是如果我們需要幾天怎麼辦?也許我們正在編寫一個函式來繪製一週中的家務,並且不想使用 list —— 我們可以使用不同型別的 Enum

>>> from enum import Flag
>>> class Weekday(Flag):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 4
...     THURSDAY = 8
...     FRIDAY = 16
...     SATURDAY = 32
...     SUNDAY = 64

我們改變了兩件事:我們繼承自 Flag,並且所有的值都是 2 的冪。

就像上面的原始 Weekday 列舉一樣,我們可以進行單選

>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>

但是 Flag 也允許我們將幾個成員組合成一個變數

>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>

您甚至可以遍歷一個 Flag 變數

>>> for day in weekend:
...     print(day)
Weekday.SATURDAY
Weekday.SUNDAY

好的,讓我們設定一些家務

>>> chores_for_ethan = {
...     'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
...     'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
...     'answer SO questions': Weekday.SATURDAY,
...     }

以及一個用於顯示給定日期家務的函式

>>> def show_chores(chores, day):
...     for chore, days in chores.items():
...         if day in days:
...             print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions

在成員的實際值無關緊要的情況下,您可以省去一些工作,並使用 auto() 來表示值

>>> from enum import auto
>>> class Weekday(Flag):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = auto()
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = auto()
...     WEEKEND = SATURDAY | SUNDAY

程式化訪問列舉成員及其屬性

有時以程式設計方式訪問列舉中的成員很有用(即在程式編寫時不知道確切顏色而 Color.RED 不夠用的情況)。Enum 允許這種訪問

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

如果要按 名稱 訪問列舉成員,請使用項訪問

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

如果您有一個列舉成員並且需要它的 namevalue

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

複製列舉成員和值

具有相同名稱的兩個列舉成員是無效的

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2

但是,一個列舉成員可以有其他與之關聯的名稱。給定具有相同值的兩個條目 AB(並且 A 首先定義),B 是成員 A 的別名。按值查詢 A 的值將返回成員 A。按名稱查詢 A 將返回成員 A。按名稱查詢 B 也將返回成員 A

>>> class Shape(Enum):
...     SQUARE = 2
...     DIAMOND = 1
...     CIRCLE = 3
...     ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>

備註

嘗試建立與已定義屬性(另一個成員、方法等)同名的成員,或嘗試建立與成員同名的屬性是不允許的。

確保列舉值唯一

預設情況下,列舉允許將多個名稱作為相同值的別名。當不需要此行為時,您可以使用 unique() 裝飾器

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

使用自動值

如果確切值不重要,您可以使用 auto

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]

這些值由 _generate_next_value_() 選擇,可以被覆蓋

>>> class AutoName(Enum):
...     @staticmethod
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']

備註

方法 _generate_next_value_() 必須在任何成員之前定義。

迭代

遍歷列舉成員不提供別名

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]

請注意,別名 Shape.ALIAS_FOR_SQUAREWeekday.WEEKEND 未顯示。

特殊屬性 __members__ 是一個只讀的有序名稱到成員的對映。它包括列舉中定義的所有名稱,包括別名

>>> for name, member in Shape.__members__.items():
...     name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

__members__ 屬性可用於對列舉成員進行詳細的程式化訪問。例如,查詢所有別名

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

備註

標誌的別名包括設定了多個標誌的值,例如 3,以及未設定任何標誌的值,即 0

比較操作

列舉成員透過身份進行比較

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

不支援列舉值之間的有序比較。列舉成員不是整數(但請參閱下面的 IntEnum

>>> Color.RED < Color.BLUE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'

但定義了相等比較

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

與非列舉值進行比較將始終比較不相等(同樣,IntEnum 明確設計為行為不同,請參見下文)

>>> Color.BLUE == 2
False

警告

可以重新載入模組——如果重新載入的模組包含列舉,它們將被重新建立,並且新成員可能不會與原始成員進行身份/相等比較。

列舉允許的成員和屬性

上面的大多數示例都使用整數作為列舉值。使用整數簡短方便(並且由 函式式 API 預設提供),但並非嚴格強制。在絕大多數用例中,人們並不關心列舉的實際值是什麼。但如果值 很重要,列舉可以有任意值。

列舉是 Python 類,可以像往常一樣擁有方法和特殊方法。如果我們有這個列舉

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # self is the member here
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls here is the enumeration
...         return cls.HAPPY
...

然後

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'

允許的規則如下:以單個下劃線開頭和結尾的名稱由列舉保留,不能使用;列舉中定義的所有其他屬性都將成為此列舉的成員,但特殊方法(__str__()__add__() 等)、描述符(方法也是描述符)以及 _ignore_ 中列出的變數名稱除外。

注意:如果您的列舉定義了 __new__() 和/或 __init__(),則傳遞給列舉成員的任何值都將傳遞給這些方法。有關示例,請參見 Planet

備註

如果定義了 __new__() 方法,則在建立列舉成員期間使用;然後它會被列舉的 __new__() 替換,後者在類建立後用於查詢現有成員。有關更多詳細資訊,請參見 何時使用 __new__() 而非 __init__()

受限的列舉子類化

新的 Enum 類必須有一個基列舉類,最多一個具體資料型別,以及所需數量的基於 object 的 mixin 類。這些基類的順序是

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

此外,只有當列舉未定義任何成員時才允許子類化列舉。因此,這是禁止的

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>

但這允許

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

允許對定義成員的列舉進行子類化會導致違反型別和例項的一些重要不變性。另一方面,允許在一組列舉之間共享一些共同行為是有意義的。(有關示例,請參見 OrderedEnum。)

Dataclass 支援

dataclass 繼承時,__repr__() 會省略繼承類的名稱。例如

>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
...     size: str
...     legs: int
...     tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
...     BEETLE = 'small', 6
...     DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>

使用 dataclass() 引數 repr=False 來使用標準 repr()

3.12 版本中的變化: 值區域中僅顯示資料類欄位,而不顯示資料類的名稱。

備註

不支援將 dataclass() 裝飾器新增到 Enum 及其子類。它不會引發任何錯誤,但在執行時會產生非常奇怪的結果,例如成員彼此相等

>>> @dataclass               # don't do this: it does not make any sense
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # problem is here: they should not be equal
True

Pickling

列舉可以被序列化和反序列化

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

序列化的常見限制適用:可序列化的列舉必須在模組的頂層定義,因為反序列化要求它們可以從該模組匯入。

備註

使用 pickle 協議版本 4,可以輕鬆地序列化巢狀在其他類中的列舉。

可以透過在列舉類中定義 __reduce_ex__() 來修改列舉成員的序列化/反序列化方式。預設方法是按值,但具有複雜值的列舉可能希望按名稱使用

>>> import enum
>>> class MyEnum(enum.Enum):
...     __reduce_ex__ = enum.pickle_by_enum_name

備註

不建議對標誌使用按名稱,因為未命名的別名將無法反序列化。

函式式 API

Enum 類是可呼叫的,提供了以下函式式 API

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

此 API 的語義類似於 namedtupleEnum 呼叫的第一個引數是列舉的名稱。

第二個引數是列舉成員名稱的 來源。它可以是空格分隔的名稱字串、名稱序列、包含鍵/值對的 2 元組序列,或名稱到值的對映(例如字典)。最後兩個選項允許為列舉分配任意值;其他選項自動分配從 1 開始遞增的整數(使用 start 引數指定不同的起始值)。返回一個從 Enum 派生的新類。換句話說,上面對 Animal 的賦值等價於

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

預設從 1 開始而不是 0 的原因是 0 在布林意義上是 False,而預設情況下所有列舉成員都評估為 True

使用函式式 API 建立的列舉的序列化可能會很棘手,因為框架堆疊實現細節被用於嘗試找出列舉是在哪個模組中建立的(例如,如果您在單獨的模組中使用實用函式,它將失敗,並且在 IronPython 或 Jython 上也可能不起作用)。解決方案是明確指定模組名稱,如下所示

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

警告

如果未提供 module,並且 Enum 無法確定它是哪個模組,則新的 Enum 成員將無法反序列化;為了使錯誤更接近源頭,序列化將被停用。

新的 pickle 協議 4 在某些情況下還依賴於將 __qualname__ 設定為 pickle 能夠找到類的位置。例如,如果該類在全域性範圍的 SomeData 類中可用

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

完整的簽名是

Enum(
    value='NewEnumName',
    names=<...>,
    *,
    module='...',
    qualname='...',
    type=<mixed-in class>,
    start=1,
    )
  • value:新列舉類將記錄為其名稱的內容。

  • names:列舉成員。這可以是空格或逗號分隔的名稱字串(除非另有指定,否則值將從 1 開始)

    'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
    

    或名稱迭代器

    ['RED', 'GREEN', 'BLUE']
    

    或 (name, value) 對的迭代器

    [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
    

    或對映

    {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
    
  • module:可以在其中找到新列舉類的模組名稱。

  • qualname:可以在模組中找到新列舉類的位置。

  • type:混合到新列舉類中的型別。

  • start:如果僅傳入名稱,則開始計數的數字。

3.5 版本中的變化: 添加了 start 引數。

派生列舉

IntEnum

Enum 的第一個變體也是 int 的子類。IntEnum 的成員可以與整數進行比較;透過擴充套件,不同型別的整數列舉也可以相互比較

>>> from enum import IntEnum
>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Request(IntEnum):
...     POST = 1
...     GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True

但是,它們仍然不能與標準 Enum 列舉進行比較

>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False

IntEnum 值在其他方面也像您期望的那樣表現得像整數

>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]

StrEnum

提供的 Enum 的第二個變體也是 str 的子類。StrEnum 的成員可以與字串進行比較;透過擴充套件,不同型別的字串列舉也可以相互比較。

在 3.11 版本中新增。

IntFlag

提供的 Enum 的下一個變體,IntFlag,也基於 int。區別在於 IntFlag 成員可以使用位運算子(&, |, ^, ~)進行組合,如果可能的話,結果仍然是 IntFlag 成員。像 IntEnum 一樣,IntFlag 成員也是整數,可以在任何使用 int 的地方使用。

備註

IntFlag 成員執行除位運算之外的任何操作都會使其失去 IntFlag 成員資格。

位運算導致無效 IntFlag 值將失去 IntFlag 成員資格。有關詳細資訊,請參見 FlagBoundary

在 3.6 版本加入。

3.11 版中已更改。

示例 IntFlag

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

也可以命名組合

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>

備註

命名組合被認為是別名。別名在迭代期間不會出現,但可以從按值查詢中返回。

3.11 版中已更改。

IntFlagEnum 之間的另一個重要區別是,如果沒有設定任何標誌(值為 0),則其布林評估為 False

>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False

由於 IntFlag 成員也是 int 的子類,因此它們可以與它們結合使用(但可能會失去 IntFlag 成員資格

>>> Perm.X | 4
<Perm.R|X: 5>

>>> Perm.X + 8
9

備註

否定運算子 ~ 始終返回一個具有正值的 IntFlag 成員

>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True

IntFlag 成員也可以迭代

>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]

在 3.11 版本中新增。

Flag

最後一個變體是 Flag。像 IntFlag 一樣,Flag 成員可以使用位運算子(&、|、^、~)進行組合。與 IntFlag 不同,它們不能與任何其他 Flag 列舉或 int 組合或比較。雖然可以直接指定值,但建議使用 auto 作為值,並讓 Flag 選擇合適的值。

在 3.6 版本加入。

IntFlag 一樣,如果 Flag 成員的組合導致沒有設定任何標誌,則布林評估為 False

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False

單個標誌的值應該是 2 的冪(1、2、4、8…),而標誌的組合則不是

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

為“未設定任何標誌”條件命名不會改變其布林值

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Flag 成員也可以迭代

>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]

在 3.11 版本中新增。

備註

對於大多數新程式碼,強烈推薦使用 EnumFlag,因為 IntEnumIntFlag 破壞了列舉的一些語義承諾(透過與整數可比較,從而透過傳遞性與其他不相關的列舉可比較)。IntEnumIntFlag 應該僅在 EnumFlag 不可行的情況下使用;例如,當整數常量被列舉替換時,或者為了與其他系統互操作。

其他

雖然 IntEnumenum 模組的一部分,但獨立實現它會非常簡單

class IntEnum(int, ReprEnum):   # or Enum instead of ReprEnum
    pass

這演示瞭如何定義類似的派生列舉;例如,一個混合了 float 而不是 intFloatEnum

一些規則

  1. 子類化 Enum 時,mixin 型別必須出現在基類序列中的 Enum 類本身之前,如上面的 IntEnum 示例所示。

  2. 混合型別必須是可子類化的。例如,boolrange 不可子類化,如果在 Enum 建立時用作混合型別,將引發錯誤。

  3. 雖然 Enum 可以有任何型別的成員,但一旦混合了額外型別,所有成員的值都必須是該型別,例如上面是 int。此限制不適用於僅新增方法而不指定其他型別的混合。

  4. 當混合其他資料型別時,value 屬性與列舉成員本身 不同,儘管它們等價並且會比較相等。

  5. 一個 data type 是一個定義了 __new__() 的 mixin,或者是一個 dataclass

  6. %-風格格式:%s%r 分別呼叫 Enum 類的 __str__()__repr__();其他程式碼(例如 IntEnum 的 %i%h)將列舉成員視為其混合型別。

  7. 格式化字串字面量str.format()format() 將使用列舉的 __str__() 方法。

備註

由於 IntEnumIntFlagStrEnum 被設計為現有常量的直接替代品,它們的 __str__() 方法已重置為其資料型別的 __str__() 方法。

何時使用 __new__() 而非 __init__()

每當您想自定義 Enum 成員的實際值時,都必須使用 __new__()。任何其他修改都可以放在 __new__()__init__() 中,首選 __init__()

例如,如果您想將多個專案傳遞給建構函式,但只想其中一個作為值

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

警告

不要 呼叫 super().__new__(),因為找到的是隻用於查詢的 __new__;相反,直接使用資料型別。

細節

支援的 __dunder__ 名稱

__members__ 是一個只讀的有序對映,包含 member_name:member 項。它只在類上可用。

如果指定了 __new__(),它必須建立並返回列舉成員;同時,適當地設定成員的 _value_ 也是一個非常好的主意。一旦所有成員都建立完畢,它就不再使用了。

支援的 _sunder_ 名稱

  • _name_ – 成員的名稱

  • _value_ – 成員的值;可以在 __new__ 中設定

  • _missing_() – 當找不到值時使用的查詢函式;可以被覆蓋

  • _ignore_ – 名稱列表,可以是 liststr,它們不會轉換為成員,並將從最終類中移除

  • _generate_next_value_() – 用於獲取列舉成員的適當值;可以被覆蓋

  • _add_alias_() – 將新名稱新增為現有成員的別名。

  • _add_value_alias_() – 將新值新增為現有成員的別名。有關示例,請參見 MultiValueEnum

    備註

    對於標準 Enum 類,選擇的下一個值是已見到的最大值加一。

    對於 Flag 類,選擇的下一個值將是下一個最大的 2 的冪。

    3.13 版本中的變化: 早期版本使用最後看到的值而不是最大值。

3.6 版本新增: _missing__order__generate_next_value_

3.7 版本新增: _ignore_

3.13 版本新增: _add_alias__add_value_alias_

為了幫助保持 Python 2 / Python 3 程式碼同步,可以提供 _order_ 屬性。它將與列舉的實際順序進行檢查,如果兩者不匹配則引發錯誤

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_:
  ['RED', 'BLUE', 'GREEN']
  ['RED', 'GREEN', 'BLUE']

備註

在 Python 2 程式碼中,_order_ 屬性是必要的,因為在記錄之前定義順序會丟失。

_Private__names

私有名稱 不會轉換為列舉成員,而是保持普通屬性。

3.11 版中已更改。

Enum 成員型別

列舉成員是其列舉類的例項,通常以 EnumClass.member 的形式訪問。在某些情況下,例如編寫自定義列舉行為時,能夠直接從另一個成員訪問一個成員很有用,並且受到支援;但是,為了避免成員名稱與混合類中的屬性/方法之間的名稱衝突,強烈建議使用大寫名稱。

3.5 版本中的變化。

建立與其他資料型別混合的成員

當將其他資料型別(例如 intstr)與 Enum 子類化時,= 之後的所有值都傳遞給該資料型別的建構函式。例如

>>> class MyEnum(IntEnum):      # help(int) -> int(x, base=10) -> integer
...     example = '11', 16      # so x='11' and base=16
...
>>> MyEnum.example.value        # and hex(11) is...
17

Enum 類和成員的布林值

與非 Enum 型別(例如 intstr 等)混合的列舉類根據混合型別的規則進行評估;否則,所有成員都評估為 True。要使您自己的列舉的布林評估取決於成員的值,請將以下內容新增到您的類中

def __bool__(self):
    return bool(self.value)

純粹的 Enum 類始終評估為 True

帶方法的 Enum

如果您的列舉子類有額外的方法,就像下面的 Planet 類一樣,這些方法將出現在成員的 dir() 中,但不會出現在類的 dir()

>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']

組合 Flag 成員

迭代 Flag 成員的組合將只返回由單個位組成的成員

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.RED|GREEN|BLUE: 7>

FlagIntFlag 細節

使用以下程式碼片段作為我們的示例

>>> class Color(IntFlag):
...     BLACK = 0
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     PURPLE = RED | BLUE
...     WHITE = RED | GREEN | BLUE
...

以下為真

  • 單位元標誌是規範的

  • 多位和零位標誌是別名

  • 迭代期間只返回規範標誌

    >>> list(Color.WHITE)
    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
    
  • 否定一個標誌或標誌集會返回一個具有相應正整數值的新標誌/標誌集

    >>> Color.BLUE
    <Color.BLUE: 4>
    
    >>> ~Color.BLUE
    <Color.RED|GREEN: 3>
    
  • 偽標誌的名稱由其成員的名稱構成

    >>> (Color.RED | Color.GREEN).name
    'RED|GREEN'
    
    >>> class Perm(IntFlag):
    ...     R = 4
    ...     W = 2
    ...     X = 1
    ...
    >>> (Perm.R & Perm.W).name is None  # effectively Perm(0)
    True
    
  • 可以從操作中返回多位標誌,即別名

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # or Color(-1)
    <Color.WHITE: 7>
    
    >>> Color(0)
    <Color.BLACK: 0>
    
  • 成員資格/包含性檢查:零值標誌始終被視為包含

    >>> Color.BLACK in Color.WHITE
    True
    

    否則,只有當一個標誌的所有位都在另一個標誌中時,才返回 True

    >>> Color.PURPLE in Color.WHITE
    True
    
    >>> Color.GREEN in Color.PURPLE
    False
    

有一個新的邊界機制來控制如何處理超出範圍/無效的位:STRICTCONFORMEJECTKEEP

  • STRICT –> 遇到無效值時引發異常

  • CONFORM –> 丟棄任何無效位

  • EJECT –> 失去 Flag 狀態併成為具有給定值的普通整數

  • KEEP –> 保留額外的位

    • 保留 Flag 狀態和額外的位

    • 額外的位不會出現在迭代中

    • 額外的位會出現在 repr() 和 str() 中

Flag 的預設值為 STRICTIntFlag 的預設值為 EJECT_convert_ 的預設值為 KEEP(請參閱 ssl.Options 以獲取何時需要 KEEP 的示例)。

列舉和標誌有何不同?

列舉具有自定義的元類,它影響派生 Enum 類及其例項(成員)的許多方面。

列舉類

EnumType 元類負責提供 __contains__()__dir__()__iter__() 以及其他允許人們對 Enum 類執行在典型類上會失敗的操作的方法,例如 list(Color)some_enum_var in ColorEnumType 負責確保最終 Enum 類上的其他各種方法是正確的(例如 __new__()__getnewargs__()__str__()__repr__())。

標誌類

標誌具有擴充套件的別名檢視:要成為規範的,標誌的值需要是 2 的冪值,而不是重複的名稱。因此,除了 Enum 定義的別名之外,沒有值(又名 0)或具有多個 2 的冪值(例如 3)的標誌都被視為別名。

列舉成員(即例項)

列舉成員最有趣的地方在於它們是單例。 EnumType 在建立列舉類本身時就建立了所有成員,然後設定了一個自定義的 __new__() 來確保透過只返回現有成員例項,永遠不會例項化新的成員。

標誌成員

標誌成員可以像 Flag 類一樣迭代,並且只返回規範成員。例如

>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]

(請注意,BLACKPURPLEWHITE 沒有出現。)

反轉標誌成員會返回相應的正值,而不是負值——例如

>>> ~Color.RED
<Color.GREEN|BLUE: 6>

標誌成員的長度對應於它們包含的 2 的冪值的數量。例如

>>> len(Color.PURPLE)
2

列舉食譜

雖然 EnumIntEnumStrEnumFlagIntFlag 預計可以涵蓋大多數用例,但它們無法涵蓋所有用例。這裡有一些不同型別列舉的食譜,可以直接使用,也可以作為建立自己列舉的示例。

省略值

在許多用例中,人們並不關心列舉的實際值是什麼。有幾種方法可以定義這種簡單的列舉

  • 使用 auto 的例項作為值

  • 使用 object 的例項作為值

  • 使用描述性字串作為值

  • 使用元組作為值,並使用自定義 __new__() 將元組替換為 int

使用這些方法中的任何一種都向使用者表明這些值並不重要,並且還允許新增、刪除或重新排序成員,而無需重新編號剩餘成員。

使用 auto

使用 auto 將如下所示

>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>

使用 object

使用 object 將如下所示

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>

這也是為什麼您可能想編寫自己的 __repr__() 的一個很好的例子

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...     def __repr__(self):
...         return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>

使用描述性字串

使用字串作為值將如下所示

>>> class Color(Enum):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>

使用自定義 __new__()

使用自動編號的 __new__() 將如下所示

>>> class AutoNumber(Enum):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...
>>> class Color(AutoNumber):
...     RED = ()
...     GREEN = ()
...     BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>

要建立一個更通用的 AutoNumber,請在簽名中新增 *args

>>> class AutoNumber(Enum):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

然後,當您從 AutoNumber 繼承時,您可以編寫自己的 __init__ 來處理任何額外的引數

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

備註

如果定義了 __new__() 方法,則在建立列舉成員期間使用;然後它會被列舉的 __new__() 替換,後者在類建立後用於查詢現有成員。

警告

不要 呼叫 super().__new__(),因為找到的是隻用於查詢的 __new__;相反,直接使用資料型別——例如

obj = int.__new__(cls, value)

OrderedEnum

一個不基於 IntEnum 的有序列舉,因此保持了正常的 Enum 不變數(例如不能與其他列舉進行比較)

>>> class OrderedEnum(Enum):
...     def __ge__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value >= other.value
...         return NotImplemented
...     def __gt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value > other.value
...         return NotImplemented
...     def __le__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value <= other.value
...         return NotImplemented
...     def __lt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value < other.value
...         return NotImplemented
...
>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True

DuplicateFreeEnum

如果找到重複的成員值,則引發錯誤而不是建立別名

>>> class DuplicateFreeEnum(Enum):
...     def __init__(self, *args):
...         cls = self.__class__
...         if any(self.value == e.value for e in cls):
...             a = self.name
...             e = cls(self.value).name
...             raise ValueError(
...                 "aliases not allowed in DuplicateFreeEnum:  %r --> %r"
...                 % (a, e))
...
>>> class Color(DuplicateFreeEnum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...     GRENE = 2
...
Traceback (most recent call last):
  ...
ValueError: aliases not allowed in DuplicateFreeEnum:  'GRENE' --> 'GREEN'

備註

這是一個有用的示例,用於子類化 Enum 以新增或更改其他行為以及禁止別名。如果唯一期望的更改是禁止別名,則可以使用 unique() 裝飾器。

MultiValueEnum

支援每個成員擁有多個值

>>> class MultiValueEnum(Enum):
...     def __new__(cls, value, *values):
...         self = object.__new__(cls)
...         self._value_ = value
...         for v in values:
...             self._add_value_alias_(v)
...         return self
...
>>> class DType(MultiValueEnum):
...     float32 = 'f', 8
...     double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>

Planet

如果定義了 __new__()__init__(),列舉成員的值將傳遞給這些方法

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

TimePeriod

使用 _ignore_ 屬性的示例

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

子類化 EnumType

儘管大多數列舉需求可以透過使用類裝飾器或自定義函式來定製 Enum 子類來滿足,但可以子類化 EnumType 以提供不同的列舉體驗。