enum --- 枚舉類型支持?

3.4 新版功能.

源代碼: Lib/enum.py


枚舉是一組符號名稱(枚舉成員)的集合,枚舉成員應(yīng)該是唯一的、不可變的。在枚舉中,可以對成員進(jìn)行恒等比較,并且枚舉本身是可迭代的。

模塊內(nèi)容?

此模塊定義了四個(gè)枚舉類,它們可被用來定義名稱和值的不重復(fù)集合: Enum, IntEnum, FlagIntFlag。 此外還定義了一個(gè)裝飾器 unique() 和一個(gè)輔助類 auto。

class enum.Enum?

用于創(chuàng)建枚舉型常數(shù)的基類。 請參閱 Functional API 小節(jié)了解另一種替代性的構(gòu)建語法。

class enum.IntEnum?

用于創(chuàng)建同時(shí)也是 int 的子類的枚舉型常數(shù)的基類。

class enum.IntFlag?

此基類用于創(chuàng)建可使用按位運(yùn)算符進(jìn)行組合而不會丟失其 IntFlag 成員資格的枚舉常量。 IntFlag 成員同樣也是 int 的子類。

class enum.Flag?

此基類用于創(chuàng)建枚舉常量 可使用按位運(yùn)算符進(jìn)行組合而不會丟失其 Flag 成員資格的枚舉常量。

enum.unique()?

此 Enum 類裝飾器可確保只將一個(gè)名稱綁定到任意一個(gè)值。

class enum.auto?

實(shí)例會被替換為一個(gè)可作為 Enum 成員的適當(dāng)?shù)闹怠?初始值從 1 開始。

3.6 新版功能: Flag, IntFlag, auto

創(chuàng)建一個(gè) Enum?

枚舉是使用 class 語法來創(chuàng)建的,這使得它們易于讀寫。 另一種替代創(chuàng)建方法的描述見 Functional API。 要定義一個(gè)枚舉,可以對 Enum 進(jìn)行如下的子類化:

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

注解

Enum 的成員值

成員值可以為任意類型: int, str 等等。 如果具體的值不重要,你可以使用 auto 實(shí)例,將為你選擇適當(dāng)?shù)闹怠?但如果你混用 auto 與其他值則需要小心謹(jǐn)慎。

注解

命名法

  • Color 是一個(gè) enumeration (或稱 enum)

  • 屬性 Color.RED, Color.GREEN 等等是 枚舉成員 (或稱 enum 成員) 并且被用作常量。

  • 枚舉成員具有 名稱 (例如 Color.RED 的名稱為 RED,Color.BLUE 的值為 3 等等)

注解

雖然我們使用 class 語法來創(chuàng)建 Enum,但 Enum 并不是普通的 Python 類。 更多細(xì)節(jié)請參閱 How are Enums different?。

枚舉成員具有適合人類閱讀的表示形式:

>>> print(Color.RED)
Color.RED

...而它們的 repr 包含更多信息:

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

一個(gè)枚舉成員的 type 就是它所從屬的枚舉:

>>> type(Color.RED)
<enum 'Color'>
>>> isinstance(Color.GREEN, Color)
True
>>>

Enum 的成員還有一個(gè)包含其條目名稱的特征屬性:

>>> print(Color.RED.name)
RED

枚舉支持按照定義順序進(jìn)行迭代:

>>> class Shake(Enum):
...     VANILLA = 7
...     CHOCOLATE = 4
...     COOKIES = 9
...     MINT = 3
...
>>> for shake in Shake:
...     print(shake)
...
Shake.VANILLA
Shake.CHOCOLATE
Shake.COOKIES
Shake.MINT

枚舉成員是可哈希的,因此它們可在字典和集合中可用:

>>> apples = {}
>>> apples[Color.RED] = 'red delicious'
>>> apples[Color.GREEN] = 'granny smith'
>>> apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'}
True

對枚舉成員及其屬性的程序化訪問?

有時(shí)對枚舉中的成員進(jìn)行程序化訪問是很有用的(例如在某些場合不能使用 Color.RED 因?yàn)樵诰幊虝r(shí)并不知道要指定的確切顏色)。 Enum 允許這樣的訪問:

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

如果你希望通過 name 來訪問枚舉成員,可使用條目訪問:

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

如果你有一個(gè)枚舉成員并且需要它的 namevalue:

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

復(fù)制枚舉成員和值?

不允許有同名的枚舉成員:

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'SQUARE'

但是,允許兩個(gè)枚舉成員有相同的值。 假定兩個(gè)成員 A 和 B 有相同的值(且 A 先被定義),則 B 就是 A 的一個(gè)別名。 按值查找 A 和 B 的值將返回 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>

注解

試圖創(chuàng)建具有與某個(gè)已定義的屬性(另一個(gè)成員或方法等)相同名稱的成員或者試圖創(chuàng)建具有相同名稱的屬性也是不允許的。

確保唯一的枚舉值?

默認(rèn)情況下,枚舉允許有多個(gè)名稱作為某個(gè)相同值的別名。 如果不想要這樣的行為,可以使用以下裝飾器來確保每個(gè)值在枚舉中只被使用一次:

@enum.unique

專用于枚舉的 class 裝飾器。 它會搜索一個(gè)枚舉的 __members__ 并收集所找到的任何別名;只要找到任何別名就會引發(fā) ValueError 并附帶相關(guān)細(xì)節(jié)信息:

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

使用自動設(shè)定的值?

如果確切的值不重要,你可以使用 auto:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

值將由 _generate_next_value_() 來選擇,該函數(shù)可以被重載:

>>> class AutoName(Enum):
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

注解

默認(rèn) _generate_next_value_() 方法的目標(biāo)是提供所給出的最后一個(gè) int 所在序列的下一個(gè) int,但這種行為方式屬于實(shí)現(xiàn)細(xì)節(jié)并且可能發(fā)生改變。

注解

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

迭代?

對枚舉成員的迭代不會給出別名:

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]

特殊屬性 __members__ 是一個(gè)將名稱映射到成員的有序字典。 它包含枚舉中定義的所有名稱,包括別名:

>>> 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__ 屬性可被用于對枚舉成員進(jìn)行詳細(xì)的程序化訪問。 例如,找出所有別名:

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

比較運(yùn)算?

枚舉成員是按標(biāo)識號進(jìn)行比較的:

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

枚舉值之間的排序比較 不被 支持。 Enum 成員不屬于整數(shù) (另請參閱下文的 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 被顯式設(shè)計(jì)成不同的行為,參見下文):

>>> Color.BLUE == 2
False

允許的枚舉成員和屬性?

以上示例使用整數(shù)作為枚舉值。 使用整數(shù)相當(dāng)簡潔方便(并由 Functional API 默認(rèn)提供),但并不強(qiáng)制要求使用。 在大部分用例中,開發(fā)者都關(guān)心枚舉的實(shí)際值是什么。 但如果值 確實(shí) 重要,則枚舉可以使用任意的值。

枚舉屬于 Python 的類,并可具有普通方法和特殊方法。 如果我們有這樣一個(gè)枚舉:

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

對于允許內(nèi)容的規(guī)則如下:以單下劃線開頭和結(jié)尾的名稱是由枚舉保留而不可使用;在枚舉中定義的所有其他屬性將成為該枚舉的成員,例外項(xiàng)則包括特殊方法成員 (__str__(), __add__() 等),描述符 (方法也屬于描述符) 以及在 _ignore_ 中列出的變量名。

注意:如果你的枚舉定義了 __new__() 和/或 __init__() 那么指定給枚舉成員的任何值都會被傳入這些方法。 請參閱示例 Planet。

受限的 Enum 子類化?

一個(gè)新的 Enum 類必須基于一個(gè) Enum 類,至多一個(gè)實(shí)體數(shù)據(jù)類型以及出于實(shí)際需要的任意多個(gè)基于 object 的 mixin 類。 這些基類的順序?yàn)?

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

另外,僅當(dāng)一個(gè)枚舉未定義任何成員時(shí)才允許子類化該枚舉。 因此禁止以下寫法:

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: Cannot extend enumerations

但是允許這樣的寫法:

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

允許子類化定義了成員的枚舉將會導(dǎo)致違反類型與實(shí)例的某些重要的不可變規(guī)則。 在另一方面,允許在一組枚舉之間共享某些通用行為也是有意義的。 (請參閱示例 OrderedEnum 。)

封存?

枚舉可以被封存與解封:

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

封存的常規(guī)限制同樣適用:可封存枚舉必須在模塊的最高層級中定義,因?yàn)榻夥獠僮饕笏鼈兛梢詮脑撃K導(dǎo)入。

注解

使用 pickle 協(xié)議版本 4 可以方便地封存嵌套在其他類中的枚舉。

通過在枚舉類中定義 __reduce_ex__() 可以對 Enum 成員的封存/解封方式進(jìn)行修改。

可用 API?

Enum 類屬于可調(diào)用對象,它提供了以下可用的 API:

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

該 API 的主義類似于 namedtuple。 調(diào)用 Enum 的第一個(gè)參數(shù)是枚舉的名稱。

第二個(gè)參數(shù)是枚舉成員名稱的 來源。 它可以是一個(gè)用空格分隔的名稱字符串、名稱序列、鍵/值對 2 元組的序列,或者名稱到值的映射(例如字典)。 最后兩種選項(xiàng)使得可以為枚舉任意賦值;其他選項(xiàng)會自動以從 1 開始遞增的整數(shù)賦值(使用 start 形參可指定不同的起始值)。 返回值是一個(gè)派生自 Enum 的新類。 換句話說,以上對 Animal 的賦值就等價(jià)于:

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

默認(rèn)以 1 而以 0 作為起始數(shù)值的原因在于 0 的布爾值為 False,但所有枚舉成員都應(yīng)被求值為 True

封存通過功能性 API 創(chuàng)建的枚舉可能會有點(diǎn)麻煩,因?yàn)橐褂脦褩5膶?shí)現(xiàn)細(xì)節(jié)來嘗試并找出枚舉是在哪個(gè)模塊中創(chuàng)建的(例如,當(dāng)你使用不同模塊的工具函數(shù)時(shí)可能會失敗,在 IronPython 或 Jython 上也可能會沒有效果)。 解決辦法是顯式地指定模塊名稱,如下所示:

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

警告

如果未提供 module,且 Enum 無法確定是哪個(gè)模塊,新的 Enum 成員將不可被解封;為了讓錯(cuò)誤盡量靠近源頭,封存將被禁用。

新的 pickle 協(xié)議版本 4 在某些情況下同樣依賴于 __qualname__ 被設(shè)為特定位置以便 pickle 能夠找到相應(yīng)的類。 例如,類是否存在于全局作用域的 SomeData 類中:

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

完整的簽名為:

Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)

將被新 Enum 類將記錄為其名稱的數(shù)據(jù)。

names

Enum 的成員。 這可以是一個(gè)空格或逗號分隔的字符串 (起始值將為 1,除非另行指定):

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

或是一個(gè)名稱的迭代器:

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

或是一個(gè) (名稱, 值) 對的迭代器:

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

或是一個(gè)映射:

{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
module

新 Enum 類所在模塊的名稱。

qualname

新 Enum 類在模塊中的具體位置。

type

要加入新 Enum 類的類型。

start

當(dāng)只傳入名稱時(shí)要使用的起始數(shù)值。

在 3.5 版更改: 增加了 start 形參。

派生的枚舉?

IntEnum?

所提供的第一個(gè)變種 Enum 同時(shí)也是 int 的一個(gè)子類。 IntEnum 的成員可與整數(shù)進(jìn)行比較;通過擴(kuò)展,不同類型的整數(shù)枚舉也可以相互進(jìn)行比較:

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

不過,它們?nèi)匀徊豢膳c標(biāo)準(zhǔn) Enum 枚舉進(jìn)行比較:

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

IntEnum 值在其他方面的行為都如你預(yù)期的一樣類似于整數(shù):

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

IntFlag?

所提供的下一個(gè) Enum 的變種 IntFlag 同樣是基于 int 的,不同之處在于 IntFlag 成員可使用按位運(yùn)算符 (&, |, ^, ~) 進(jìn)行合并且結(jié)果仍然為 IntFlag 成員。 如果,正如名稱所表明的,IntFlag 成員同時(shí)也是 int 的子類,并能在任何使用 int 的場合被使用。 IntFlag 成員進(jìn)行除按位運(yùn)算以外的其他運(yùn)算都將導(dǎo)致失去 IntFlag 成員資格。

3.6 新版功能.

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

對于組合同樣可以進(jìn)行命名:

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

IntFlagEnum 的另一個(gè)重要區(qū)別在于如果沒有設(shè)置任何旗標(biāo)(值為 0),則其布爾值為 False:

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

由于 IntFlag 成員同時(shí)也是 int 的子類,因此它們可以相互組合:

>>> Perm.X | 8
<Perm.8|X: 9>

標(biāo)志?

最后一個(gè)變種是 Flag。 與 IntFlag 類似,Flag 成員可使用按位運(yùn)算符 (&, |, ^, ~) 進(jìn)行組合,與 IntFlag 不同的是它們不可與任何其它 Flag 枚舉或 int 進(jìn)行組合或比較。 雖然可以直接指定值,但推薦使用 auto 作為值以便讓 Flag 選擇適當(dāng)?shù)闹怠?/p>

3.6 新版功能.

IntFlag 類似,如果 Flag 成員的某種組合導(dǎo)致沒有設(shè)置任何旗標(biāo),則其布爾值為 False:

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

單個(gè)旗標(biāo)的值應(yīng)當(dāng)為二的乘方 (1, 2, 4, 8, ...),旗標(biāo)的組合則無此限制:

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

對 "no flags set" 條件指定一個(gè)名稱并不會改變其布爾值:

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

注解

對于大多數(shù)新代碼,強(qiáng)烈推薦使用 EnumFlag,因?yàn)?IntEnumIntFlag 打破了枚舉的某些語義約定(例如可以同整數(shù)進(jìn)行比較,并因而導(dǎo)致此行為被傳遞給其他無關(guān)的枚舉)。 IntEnumIntFlag 的使用應(yīng)當(dāng)僅限于 EnumFlag 無法使用的場合;例如,當(dāng)使用枚舉替代整數(shù)常量時(shí),或是與其他系統(tǒng)進(jìn)行交互操作時(shí)。

其他事項(xiàng)?

雖然 IntEnumenum 模塊的一部分,但要獨(dú)立實(shí)現(xiàn)也應(yīng)該相當(dāng)容易:

class IntEnum(int, Enum):
    pass

這里演示了如何定義類似的派生枚舉;例如一個(gè)混合了 str 而不是 intStrEnum

幾條規(guī)則:

  1. 當(dāng)子類化 Enum 時(shí),在基類序列中的混合類型必須出現(xiàn)于 Enum 本身之前,如以上 IntEnum 的例子所示。

  2. 雖然 Enum 可以擁有任意類型的成員,不過一旦你混合了附加類型,則所有成員必須為相應(yīng)類型的值,如在上面的例子中即為 int。 此限制不適用于僅添加方法而未指定另一數(shù)據(jù)類型如 intstr 的混合類。

  3. 當(dāng)混合了另一數(shù)據(jù)類型時(shí),value 屬性會 不同于 枚舉成員自身,但它們?nèi)员3值葍r(jià)且比較結(jié)果也相等。

  4. %-style formatting: %s%r 會分別調(diào)用 Enum 類的 __str__()__repr__();其他代碼 (例如表示 IntEnum 的 %i%h) 會將枚舉成員視為對應(yīng)的混合類型。

  5. 格式化字符串字面值, str.format()format() 將使用混合類型的 __format__()。 如果需要 Enum 類的 str()repr(),請使用 !s!r 格式代碼。

有趣的示例?

雖然 Enum, IntEnum, IntFlagFlag 預(yù)期可覆蓋大多數(shù)應(yīng)用場景,但它們無法覆蓋全部。 這里有一些不同類型枚舉的方案,它們可以被直接使用,或是作為自行創(chuàng)建的參考示例。

省略值?

在許多應(yīng)用場景中人們都不關(guān)心枚舉的實(shí)際值是什么。 有幾個(gè)方式可以定義此種類型的簡單枚舉:

  • 使用 auto 的實(shí)例作為值

  • 使用 object 的實(shí)例作為值

  • 使用描述性的字符串作為值

  • 使用元組作為值并用自定義的 __new__() 以一個(gè) int 值來替代該元組

使用以上任何一種方法均可向用戶指明值并不重要,并且使人能夠添加、移除或重排序成員而不必改變其余成員的數(shù)值。

無論你選擇何種方法,你都應(yīng)當(dāng)提供一個(gè) repr() 并且它也需要隱藏(不重要的)值:

>>> class NoValue(Enum):
...     def __repr__(self):
...         return '<%s.%s>' % (self.__class__.__name__, self.name)
...

使用 auto?

使用 auto 看起來是這樣:

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

使用 object?

使用 object 的形式如下:

>>> class Color(NoValue):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN>

使用描述性字符串?

使用字符串作為值的形式如下:

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

使用自定義的 __new__()?

使用自動編號 __new__() 的形式如下:

>>> class AutoNumber(NoValue):
...     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>
>>> Color.GREEN.value
2

注解

如果定義了 __new__() 則它會在創(chuàng)建 Enum 成員期間被使用;隨后它將被 Enum 的 __new__() 所替換,該方法會在類創(chuàng)建后被用來查找現(xiàn)有成員。

OrderedEnum?

一個(gè)有序枚舉,它不是基于 IntEnum,因此保持了正常的 Enum 不變特性(例如不可與其他枚舉進(jìn)行比較):

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

如果發(fā)現(xiàn)重復(fù)的成員名稱則將引發(fā)錯(cuò)誤而不是創(chuàng)建別名:

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

注解

這個(gè)例子適用于子類化 Enum 來添加或改變禁用別名以及其他行為。 如果需要的改變只是禁用別名,也可以選擇使用 unique() 裝飾器。

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?

一個(gè)演示如何使用 _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)>]

各種枚舉有何區(qū)別??

枚舉具有自定義的元類,它會影響所派生枚舉類及其實(shí)例(成員)的各個(gè)方面。

枚舉類?

EnumMeta 元類負(fù)責(zé)提供 __contains__(), __dir__(), __iter__() 及其他方法以允許用戶通過 Enum 類來完成一般類做不到的事情,例如 list(Color)some_enum_var in Color。 EnumMeta 會負(fù)責(zé)確保最終 Enum 類中的各種其他方法是正確的 (例如 __new__(), __getnewargs__(), __str__()__repr__())。

枚舉成員(即實(shí)例)?

有關(guān)枚舉成員最有趣的特點(diǎn)是它們都是單例對象。 EnumMeta 會在創(chuàng)建 Enum 類本身時(shí)將它們?nèi)縿?chuàng)建完成,然后準(zhǔn)備好一個(gè)自定義的 __new__(),通過只返回現(xiàn)有的成員實(shí)例來確保不會再實(shí)例化新的對象。

細(xì)節(jié)要點(diǎn)?

支持 __dunder__ 名稱?

__members__ 是一個(gè) OrderedDict,由 member_name:member 條目組成。 它只在類上可用。

如果指定了 __new__(),它必須創(chuàng)建并返回枚舉成員;相應(yīng)地設(shè)定成員的 _value_ 也是一個(gè)很好的主意。 一旦所有成員都創(chuàng)建完成它就不會再被使用。

支持的 _sunder_ 名稱?

  • _name_ -- 成員的名稱

  • _value_ -- 成員的值;可以在 __new__ 中設(shè)置 / 修改

  • _missing_ -- 當(dāng)未發(fā)現(xiàn)某個(gè)值時(shí)所使用的查找函數(shù);可被重載

  • _ignore_ -- 一個(gè)名稱列表,可以為 list()str(),它將不會被轉(zhuǎn)化為成員,并會從最終類中被移除

  • _order_ -- 用于 Python 2/3 代碼以確保成員順序一致(類屬性,在類創(chuàng)建期間會被移除)

  • _generate_next_value_ -- 用于 Functional API 并通過 auto 為枚舉成員獲取適當(dāng)?shù)闹?;可被重載

3.6 新版功能: _missing_, _order_, _generate_next_value_

3.7 新版功能: _ignore_

用來幫助 Python 2 / Python 3 代碼保持同步提供 _order_ 屬性。 它將與枚舉的實(shí)際順序進(jìn)行對照檢查,如果兩者不匹配則會引發(fā)錯(cuò)誤:

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

注解

在 Python 2 代碼中 _order_ 屬性是必須的,因?yàn)槎x順序在被記錄之前就會丟失。

Enum 成員類型?

Enum 成員是其 Enum 類的實(shí)例,一般通過 EnumClass.member 的形式來訪問。 在特定情況下它們也可通過 EnumClass.member.member 的形式來訪問,但你絕對不應(yīng)這樣做,因?yàn)椴檎铱赡苁?,或者更糟糕地返回你所查找?Enum 成員以外的對象(這也是成員應(yīng)使用全大寫名稱的另一個(gè)好理由):

>>> class FieldTypes(Enum):
...     name = 0
...     value = 1
...     size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2

在 3.5 版更改.

Enum 類和成員的布爾值?

混合了非 Enum 類型(例如 int, str 等)的 Enum 成員會按所混合類型的規(guī)則被求值;在其他情況下,所有成員都將被求值為 True。 要使你的自定義 Enum 的布爾值取決于成員的值,請?jiān)谀愕念愔刑砑右韵麓a:

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

Enum 類總是會被求值為 True

帶有方法的 Enum?

如果你為你的 Enum 子類添加了額外的方法,如同上述的 Planet 類一樣,這些方法將在對成員執(zhí)行 dir() 時(shí)顯示出來,但對類執(zhí)行時(shí)則不會顯示:

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

組合 Flag 的成員?

如果 Flag 成員的某種組合未被命名,則 repr() 將包含所有已命名的旗標(biāo)和值中所有已命名的旗標(biāo)組合:

>>> 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.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>