如何定義和使用支援成員組合的 Python 列舉?Python 旗標介紹
先決條件
閱讀本節的先決條件是對 Python 列舉以及列舉的概念有所掌握,你可以檢視Python 列舉介紹,以及定義和使用 Python 列舉,程式設計教學的列舉介紹,以及列舉成員的組合,判斷,移除來了解相關資訊。
Python 旗標
Python 旗標(Flag)是一種特殊的 Python 列舉,他允許多個旗標(列舉)成員之間的組合操作,其組合的結果依然是一個旗標(列舉)成員。
要在 Python 中使用旗標,你需要定義從enum
模組的Flag
,IntFlag
繼承的衍生類別。
Python 旗標成員的取值範圍是什麽?
在正常情況下,Python 旗標成員的值應該是二的整數次冪2ⁿ
(n
大於等於0
),使用enum
模組的auto
類別,可以輕松實作對2ⁿ
的自動計算。旗標成員也可以是2ⁿ
以外的值,但他必須表示其他旗標成員的某種有效組合,比如,0
表示空的組合(不包含任何其他成員)。
如果一個 Python 旗標成員的取值不在以上範圍之內,那麽該成員在一些操作中可能不會發揮作用。
什麽是 Python 旗標的正規化旗標成員?
如上所述,Python 旗標中值為二的整數次冪2ⁿ
(n
大於等於0
)的成員,被稱為 Python 旗標的正規化旗標成員。
在下面的 Python 旗標COLOR
中,我們定義了成員BLACK_AND_WHITE
,他是成員WHITE
與BLACK
的組合,其值不是二的整數次冪。
from enum import *
# auto 會傳回遞增的二的整數次冪
class COLOR(Flag):
NONE = 0
WHITE = auto()
BLACK = auto()
# 因為 WHITE 為 1,BLACK 為 2,因此 3 是 WHITE 與 BLACK 的組合
BLACK_AND_WHITE = 3
RED = auto()
BLUE = auto()
GREEN = auto()
bw = COLOR.WHITE | COLOR.BLACK
# 顯示為 BLACK_AND_WHITE
print(f'{bw.name}={bw.value}')
BLACK_AND_WHITE=3
非正規化旗標成員不會出現在對 Python 旗標的疊代周遊中
對一個 Python 旗標進行疊代周遊,你將得到該旗標的所有正規化成員,至於已定義的非正規化成員則不會出現。
# 這裏不會出現 NONE 和 BLACK_AND_WHITE
for i in COLOR:
print(f'{i.name}={i.value}')
WHITE=1
BLACK=2
RED=4
BLUE=8
GREEN=16
使用 @verify 修飾詞確保 Python 旗標成員值的有效性
使用enum
模組的NAMED_FLAGS
和@verify
修飾詞,可以確保 Python 旗標成員值的有效性,比如,定義一個不存在的組合是不可行的。
在使用NAMED_FLAGS
之後,旗標DAY
的成員THURSDAY
將引發例外狀況ValueError
,因為他不是二的整數次冪,也不是已定義成員的某種組合。
@verify(NAMED_FLAGS)
class DAY(Flag):
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 4
# ERROR 9 不是二的整數次冪,也不是 1,2,4 的某種組合
THURSDAY = 9
ValueError: invalid Flag 'DAY': alias THURSDAY is missing value 0x8 [use enum.show_flag_values(value) for details]
如何取得 Python 旗標成員組合中所有的二的整數次冪?
使用enum
模組的show_flag_values
函式,可以取得某個 Python 旗標成員組合中所有的二的整數次冪2ⁿ
(n
大於等於0
),結果將作為 Python 串列物件傳回。
show_flag_values(value)
- value 參數
value
參數是 Python 旗標成員的組合。
import enum
print(enum.show_flag_values(COLOR.BLACK_AND_WHITE))
[1, 2]
取得 Python 旗標中正規化旗標成員的個數
從Flag
或IntFlag
繼承的 Python 旗標,可以使用函式len
取得正規化成員的個數。
旗標COLOR
的成員NONE
的值為0
,成員BLACK_AND_WHITE
的值為3
,因此他們不會被計算。
# COLOR 中的 NONE 和 BLACK_AND_WHITE 不會被計算
print(f'COLOR 正規化成員個數:{len(COLOR)}')
COLOR 正規化成員個數:5
組合 Python 旗標成員
從Flag
或IntFlag
繼承的 Python 旗標,可以使用或位元運算子|
,來組合 Python 旗標中的多個旗標(列舉)成員。
# 組合成員 WHITE,BLACK,RED
color = COLOR.WHITE | COLOR.BLACK | COLOR.RED
疊代周遊 Python 旗標成員組合中的正規化旗標成員
通過for
陳述式,你可以周遊 Python 旗標成員組合中所包含的正規化成員,非正規化成員不會出現在迴圈中。
# 並不會顯示非正規化成員 BLACK_AND_WHITE
for i in COLOR.BLACK_AND_WHITE | COLOR.GREEN:
print(i)
COLOR.WHITE
COLOR.BLACK
COLOR.GREEN
判斷 Python 旗標成員是否包含在旗標成員組合中
從Flag
或IntFlag
繼承的 Python 旗標,可以使用in
運算子或與位元運算子&
,來判斷組合中是否包含指定的一個或多個 Python 旗標(列舉)成員。
print(f'color 包含 GREEN?{COLOR.GREEN in color}')
print(f'color 包含 RED?{(COLOR.RED & color) == COLOR.RED}')
print(f'color 包含 BLACK_AND_WHITE?{COLOR.BLACK_AND_WHITE in color}')
color 包含 GREEN?False
color 包含 RED?True
color 包含 BLACK_AND_WHITE?True
取得 Python 旗標成員組合中正規化旗標成員的個數
對於某個 Python 旗標成員組合,函式len
可以傳回該組合中正規化旗標成員的個數。
# color 是 WHITE,BLACK,RED 的組合
print(f'color 組合中正規化成員的個數為:{len(color)}')
color 組合中正規化成員的個數為:3
判斷 Python 旗標成員組合是否為空
對於某個 Python 旗標成員組合,bool
類別的建構子可以判斷該組合是否含有任意正規化旗標成員,如果bool
傳回False
,則說明組合是空的。
當然,通過len
函式並判斷結果是否為0
,可以達到相同的效果。
# color 是 WHITE,BLACK,RED 的組合
print(f'color 組合是否為空?{not bool(color)}')
print(f'NONE 是否為空?{not bool(COLOR.NONE)}')
color 組合是否為空?False
NONE 是否為空?True
移除 Python 旗標成員組合中的旗標成員
從Flag
或IntFlag
繼承的 Python 旗標,可以使用互斥或位元運算子^
,將一個或多個 Python 旗標成員,從旗標成員組合中移除。如果需要移除的旗標成員在組合中不存在,那麽該旗標成員將被新增。
# 從 color 中移除 WHITE 和 BLACK
print(color ^ COLOR.BLACK_AND_WHITE)
# 由於 color 不包含 GREEN,因此 GREEN 將被新增
print(color ^ COLOR.GREEN)
COLOR.RED
COLOR.WHITE|BLACK|RED|GREEN
取得相反的 Python 旗標成員
從Flag
或IntFlag
繼承的 Python 旗標,可以使用反相位元運算子~
,來取得除指定旗標成員之外的所有正規化旗標成員,如果找不到符合要求的正規化成員,那麽將傳回值為0
的成員。
Python 旗標的反相運算結果僅包含已定義的正規化旗標成員
與其他程式設計語言不同,Python 旗標的反相運算結果,僅包含已定義的正規化旗標成員,不會包含任何未定義的旗標成員。
# 取得 WHITE,BLACK,RED 以外的成員
print(~color)
# 取得其他所有成員
print(~COLOR.NONE)
COLOR.BLUE|GREEN
COLOR.WHITE|BLACK|RED|BLUE|GREEN
超出取值範圍的 Python 旗標成員
每一個 Python 旗標成員都是一個旗標(列舉)的執行個體,如果在建立執行個體的過程中,指定了超出範圍的值,比如,包含了不存在的正規化旗標成員,那麽 Python 需要進行相應的處理。
你可以在定義 Python 旗標時,指定boundary
參數,該參數將決定如何處理超出取值範圍的旗標成員,他是enum
模組的以下變數之一。
- STRICT 變數
STRICT
變數表示,如果 Python 旗標成員的取值超出範圍,那麽將引發例外狀況,這是Flag
的預設設定。- CONFORM 變數
CONFORM
變數表示,如果 Python 旗標成員的取值超出範圍,那麽移除值所包含的無效成員,從而獲得一個有效的新值。- EJECT 變數
EJECT
變數表示,如果 Python 旗標成員的取值超出範圍,那麽該成員將被轉換為一個整數。- KEEP 變數
KEEP
變數表示,如果 Python 旗標成員的取值超出範圍,那麽保留值所包含的無效成員,這是IntFlag
的預設設定。
對於旗標CONFORMCOLOR
,取值5
將導致值為4
的成員被移除,最終的結果為WHITE
。對於旗標EJECTCOLOR
,取值5
將導致最終計算結果為整數5
,而不是旗標成員。對於旗標KEEPCOLOR
,取值5
將導致未定義的值為4
的成員被保留。對於旗標STRICTCOLOR
,取值3
是沒有問題的,他表示WHITE
與BLACK
的組合,取值5
將導致例外狀況ValueError
,因為他表示WHITE
與值為4
的成員的組合,而該成員並未定義。
class CONFORMCOLOR(Flag, boundary=CONFORM):
WHITE = auto()
BLACK = auto()
# 值為 4 的成員將被移除
print(CONFORMCOLOR(5))
class EJECTCOLOR(Flag, boundary=EJECT):
WHITE = auto()
BLACK = auto()
# 結果為整數 5,不再是旗標成員
print(EJECTCOLOR(5))
class KEEPCOLOR(Flag, boundary=KEEP):
WHITE = auto()
BLACK = auto()
# 4 將被保留
print(KEEPCOLOR(5))
class STRICTCOLOR(Flag, boundary=STRICT):
WHITE = auto()
BLACK = auto()
print(STRICTCOLOR(3))
# ERROR 值為 4 的成員並未定義
print(STRICTCOLOR(5))
CONFORMCOLOR.WHITE
5
KEEPCOLOR.WHITE|4
STRICTCOLOR.WHITE|BLACK
…
ValueError: invalid value 5
繼承 Python 旗標
在模組enum
中,IntFlag
是 Python 旗標類別Flag
的衍生類別,IntFlag
基底類別包括Flag
和int
,因此IntFlag
可以與其他數值進行算術運算,有效比較,或出現在任何允許使用整數的位置。當參與算術運算時,運算結果將不再是 Python 旗標成員。
class INTCOLOR(IntFlag):
WHITE = auto()
BLACK = auto()
# 參與比較
print(INTCOLOR.WHITE > 0)
# 參與算術運算,結果將不再是一個旗標成員
print(type(INTCOLOR.WHITE + INTCOLOR.BLACK))
True
<class 'int'>