如何定义和使用支持成员组合的 Python 枚举?Python 标志介绍

我被代码海扁署名-非商业-禁演绎
阅读 9:31·字数 2856·发布 
Bilibili 空间
关注 950

前提

阅读本节的前提是对 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