enum --- 枚舉類(lèi)型支持?

3.4 新版功能.

源代碼: Lib/enum.py


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

模塊內(nèi)容?

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

class enum.Enum?

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

class enum.IntEnum?

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

class enum.IntFlag?

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

class enum.Flag?

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

enum.unique()?

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

class enum.auto?

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

3.6 新版功能: Flag, IntFlag, auto

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

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

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

注解

Enum 的成員值

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

注解

命名法

  • 類(lèi) Color 是一個(gè) enumeration (或稱(chēng) enum)

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

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

注解

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

枚舉成員具有適合人類(lèi)閱讀的表示形式:

>>> 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è)包含其條目名稱(chēng)的特征屬性:

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

對(duì)枚舉成員及其屬性的程序化訪(fǎng)問(wèn)?

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

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

如果你希望通過(guò) name 來(lái)訪(fǎng)問(wèn)枚舉成員,可使用條目訪(fǎng)問(wèn):

>>> 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。 按名稱(chēng)查找 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è)成員或方法等)相同名稱(chēng)的成員或者試圖創(chuàng)建具有相同名稱(chēng)的屬性也是不允許的。

確保唯一的枚舉值?

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

@enum.unique

專(zhuān)用于枚舉的 class 裝飾器。 它會(huì)搜索一個(gè)枚舉的 __members__ 并收集所找到的任何別名;只要找到任何別名就會(huì)引發(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

使用自動(dòng)設(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_() 來(lái)選擇,該函數(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_() 方法定義必須在任何其他成員之前。

迭代?

對(duì)枚舉成員的迭代不會(huì)給出別名:

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

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

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

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

比較運(yùn)算?

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

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

枚舉值之間的排序比較 不被 支持。 Enum 成員不屬于整數(shù) (另請(qǐng)參閱下文的 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ì)成不同的行為,參見(jiàn)下文):

>>> Color.BLUE == 2
False

允許的枚舉成員和屬性?

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

枚舉屬于 Python 的類(lèi),并可具有普通方法和特殊方法。 如果我們有這樣一個(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'

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

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

受限的 Enum 子類(lèi)化?

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

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

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

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

但是允許這樣的寫(xiě)法:

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

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

封存?

枚舉可以被封存與解封:

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

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

注解

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

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

可用 API?

Enum 類(lèi)屬于可調(diào)用對(duì)象,它提供了以下可用的 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 的主義類(lèi)似于 namedtuple。 調(diào)用 Enum 的第一個(gè)參數(shù)是枚舉的名稱(chēng)。

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

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

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

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

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

警告

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

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

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

完整的簽名為:

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

將被新 Enum 類(lèi)將記錄為其名稱(chēng)的數(shù)據(jù)。

names

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

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

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

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

或是一個(gè) (名稱(chēng), 值) 對(duì)的迭代器:

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

或是一個(gè)映射:

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

新 Enum 類(lèi)所在模塊的名稱(chēng)。

qualname

新 Enum 類(lèi)在模塊中的具體位置。

type

要加入新 Enum 類(lèi)的類(lèi)型。

start

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

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

派生的枚舉?

IntEnum?

所提供的第一個(gè)變種 Enum 同時(shí)也是 int 的一個(gè)子類(lèi)。 IntEnum 的成員可與整數(shù)進(jìn)行比較;通過(guò)擴(kuò)展,不同類(lèi)型的整數(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

不過(guò),它們?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ù)期的一樣類(lèi)似于整數(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 成員。 如果,正如名稱(chēng)所表明的,IntFlag 成員同時(shí)也是 int 的子類(lèi),并能在任何使用 int 的場(chǎng)合被使用。 IntFlag 成員進(jìn)行除按位運(yùn)算以外的其他運(yùn)算都將導(dǎo)致失去 IntFlag 成員資格。

3.6 新版功能.

示例 IntFlag 類(lèi):

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

對(duì)于組合同樣可以進(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ū)別在于如果沒(méi)有設(shè)置任何旗標(biāo)(值為 0),則其布爾值為 False:

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

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

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

標(biāo)志?

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

3.6 新版功能.

IntFlag 類(lèi)似,如果 Flag 成員的某種組合導(dǎo)致沒(méi)有設(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)的組合則無(wú)此限制:

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

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

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

注解

對(duì)于大多數(shù)新代碼,強(qiáng)烈推薦使用 EnumFlag,因?yàn)?IntEnumIntFlag 打破了枚舉的某些語(yǔ)義約定(例如可以同整數(shù)進(jìn)行比較,并因而導(dǎo)致此行為被傳遞給其他無(wú)關(guān)的枚舉)。 IntEnumIntFlag 的使用應(yīng)當(dāng)僅限于 EnumFlag 無(wú)法使用的場(chǎng)合;例如,當(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

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

幾條規(guī)則:

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

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

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

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

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

有趣的示例?

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

省略值?

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

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

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

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

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

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

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

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

使用 auto?

使用 auto 看起來(lái)是這樣:

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

使用自動(dòng)編號(hào) __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__() 則它會(huì)在創(chuàng)建 Enum 成員期間被使用;隨后它將被 Enum 的 __new__() 所替換,該方法會(huì)在類(lèi)創(chuàng)建后被用來(lái)查找現(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ù)的成員名稱(chēng)則將引發(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è)例子適用于子類(lèi)化 Enum 來(lái)添加或改變禁用別名以及其他行為。 如果需要的改變只是禁用別名,也可以選擇使用 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ū)別??

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

枚舉類(lèi)?

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

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

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

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

支持 __dunder__ 名稱(chēng)?

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

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

支持的 _sunder_ 名稱(chēng)?

  • _name_ -- 成員的名稱(chēng)

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

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

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

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

  • _generate_next_value_ -- 用于 Functional API 并通過(guò) auto 為枚舉成員獲取適當(dāng)?shù)闹担豢杀恢剌d

3.6 新版功能: _missing_, _order_, _generate_next_value_

3.7 新版功能: _ignore_

用來(lái)幫助 Python 2 / Python 3 代碼保持同步提供 _order_ 屬性。 它將與枚舉的實(shí)際順序進(jìn)行對(duì)照檢查,如果兩者不匹配則會(huì)引發(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順序在被記錄之前就會(huì)丟失。

Enum 成員類(lèi)型?

Enum 成員是其 Enum 類(lèi)的實(shí)例,一般通過(guò) EnumClass.member 的形式來(lái)訪(fǎng)問(wèn)。 在特定情況下它們也可通過(guò) EnumClass.member.member 的形式來(lái)訪(fǎng)問(wèn),但你絕對(duì)不應(yīng)這樣做,因?yàn)椴檎铱赡苁。蛘吒愀獾胤祷啬闼檎业?Enum 成員以外的對(duì)象(這也是成員應(yīng)使用全大寫(xiě)名稱(chēng)的另一個(gè)好理由):

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

在 3.5 版更改.

Enum 類(lèi)和成員的布爾值?

混合了非 Enum 類(lèi)型(例如 int, str 等)的 Enum 成員會(huì)按所混合類(lèi)型的規(guī)則被求值;在其他情況下,所有成員都將被求值為 True。 要使你的自定義 Enum 的布爾值取決于成員的值,請(qǐng)?jiān)谀愕念?lèi)中添加以下代碼:

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

Enum 類(lèi)總是會(huì)被求值為 True

帶有方法的 Enum 類(lèi)?

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

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