如何定义和使用支持成员组合的 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'>