如何定義和使用支援成員組合的 Python 列舉?Python 旗標介紹

閱讀 9:36·字數 2883·發佈 
Youtube 頻道
訂閱 133

先決條件

閱讀本節的先決條件是對 Python 列舉以及列舉的概念有所掌握,你可以檢視Python 列舉介紹,以及定義和使用 Python 列舉程式設計教學列舉介紹,以及列舉成員的組合,判斷,移除來了解相關資訊。

Python 旗標

Python 旗標(Flag)是一種特殊的 Python 列舉,他允許多個旗標(列舉)成員之間的組合操作,其組合的結果依然是一個旗標(列舉)成員。

要在 Python 中使用旗標,你需要定義從enum模組的FlagIntFlag繼承的衍生類別。

Python 旗標成員的取值範圍是什麽?

在正常情況下,Python 旗標成員的值應該是二的整數次冪2ⁿn大於等於0),使用enum模組的auto類別,可以輕松實作對2ⁿ的自動計算。旗標成員也可以是2ⁿ以外的值,但他必須表示其他旗標成員的某種有效組合,比如,0表示空的組合(不包含任何其他成員)。

如果一個 Python 旗標成員的取值不在以上範圍之內,那麽該成員在一些操作中可能不會發揮作用。

什麽是 Python 旗標的正規化旗標成員?

如上所述,Python 旗標中值為二的整數次冪2ⁿn大於等於0)的成員,被稱為 Python 旗標的正規化旗標成員。

在下面的 Python 旗標COLOR中,我們定義了成員BLACK_AND_WHITE,他是成員WHITEBLACK的組合,其值不是二的整數次冪。

flags.py
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 旗標進行疊代周遊,你將得到該旗標的所有正規化成員,至於已定義的非正規化成員則不會出現。

flags.py
# 這裏不會出現 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,因為他不是二的整數次冪,也不是已定義成員的某種組合。

flags.py
@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 旗標成員的組合。

flags.py
import enum
print(enum.show_flag_values(COLOR.BLACK_AND_WHITE))
[1, 2]

取得 Python 旗標中正規化旗標成員的個數

FlagIntFlag繼承的 Python 旗標,可以使用函式len取得正規化成員的個數。

旗標COLOR的成員NONE的值為0,成員BLACK_AND_WHITE的值為3,因此他們不會被計算。

flags.py
# COLOR 中的 NONE 和 BLACK_AND_WHITE 不會被計算
print(f'COLOR 正規化成員個數:{len(COLOR)}')
COLOR 正規化成員個數:5

組合 Python 旗標成員

FlagIntFlag繼承的 Python 旗標,可以使用或位元運算子|,來組合 Python 旗標中的多個旗標(列舉)成員。

flags.py
# 組合成員 WHITE,BLACK,RED
color = COLOR.WHITE | COLOR.BLACK | COLOR.RED

疊代周遊 Python 旗標成員組合中的正規化旗標成員

通過for陳述式,你可以周遊 Python 旗標成員組合中所包含的正規化成員,非正規化成員不會出現在迴圈中。

flags.py
# 並不會顯示非正規化成員 BLACK_AND_WHITE
for i in COLOR.BLACK_AND_WHITE | COLOR.GREEN:
	print(i)
COLOR.WHITE
COLOR.BLACK
COLOR.GREEN

判斷 Python 旗標成員是否包含在旗標成員組合中

FlagIntFlag繼承的 Python 旗標,可以使用in運算子或與位元運算子&,來判斷組合中是否包含指定的一個或多個 Python 旗標(列舉)成員。

flags.py
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可以傳回該組合中正規化旗標成員的個數。

flags.py
# color 是 WHITE,BLACK,RED 的組合
print(f'color 組合中正規化成員的個數為:{len(color)}')
color 組合中正規化成員的個數為:3

判斷 Python 旗標成員組合是否為空

對於某個 Python 旗標成員組合,bool類別的建構子可以判斷該組合是否含有任意正規化旗標成員,如果bool傳回False,則說明組合是空的。

當然,通過len函式並判斷結果是否為0,可以達到相同的效果。

flags.py
# color 是 WHITE,BLACK,RED 的組合
print(f'color 組合是否為空?{not bool(color)}')
print(f'NONE 是否為空?{not bool(COLOR.NONE)}')
color 組合是否為空?False
NONE 是否為空?True

移除 Python 旗標成員組合中的旗標成員

FlagIntFlag繼承的 Python 旗標,可以使用互斥或位元運算子^,將一個或多個 Python 旗標成員,從旗標成員組合中移除。如果需要移除的旗標成員在組合中不存在,那麽該旗標成員將被新增。

flags.py
# 從 color 中移除 WHITE 和 BLACK
print(color ^ COLOR.BLACK_AND_WHITE)
# 由於 color 不包含 GREEN,因此 GREEN 將被新增
print(color ^ COLOR.GREEN)
COLOR.RED
COLOR.WHITE|BLACK|RED|GREEN

取得相反的 Python 旗標成員

FlagIntFlag繼承的 Python 旗標,可以使用反相位元運算子~,來取得除指定旗標成員之外的所有正規化旗標成員,如果找不到符合要求的正規化成員,那麽將傳回值為0的成員。

Python 旗標的反相運算結果僅包含已定義的正規化旗標成員

與其他程式設計語言不同,Python 旗標的反相運算結果,僅包含已定義的正規化旗標成員,不會包含任何未定義的旗標成員。

flags.py
# 取得 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是沒有問題的,他表示WHITEBLACK的組合,取值5將導致例外狀況ValueError,因為他表示WHITE與值為4的成員的組合,而該成員並未定義。

flags.py
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基底類別包括Flagint,因此IntFlag可以與其他數值進行算術運算,有效比較,或出現在任何允許使用整數的位置。當參與算術運算時,運算結果將不再是 Python 旗標成員。

flags.py
class INTCOLOR(IntFlag):
	WHITE = auto()
	BLACK = auto()

# 參與比較 print(INTCOLOR.WHITE > 0) # 參與算術運算,結果將不再是一個旗標成員 print(type(INTCOLOR.WHITE + INTCOLOR.BLACK))
True
<class 'int'>

程式碼

flags.py·codebeatme/python·GitHub