如何定义和使用支持成员组合的枚举?Python 标志介绍
关注 1421
前提
阅读本节的前提是对 Python 枚举以及枚举的概念有所掌握,你可以查看Python 枚举介绍,以及定义和使用枚举,编程教程的枚举介绍,以及枚举成员的组合,判断,移除来了解相关信息。
Python 标志
Python 标志(Flag)是一种特殊的枚举,他允许多个标志(枚举)成员之间的组合操作,其组合的结果依然是一个标志(枚举)成员。
要在 Python 中使用标志,你需要定义从enum
模块的Flag
,IntFlag
继承的派生类。
标志成员的取值范围是什么?
在正常情况下,标志成员的值应该是二的整数次幂2ⁿ
(n
大于等于0
),使用enum
模块的auto
类,可以轻松实现对2ⁿ
的自动计算。标志成员也可以是2ⁿ
以外的值,但他必须表示其他标志成员的某种有效组合,比如,0
表示空的组合(不包含任何其他成员)。
如果一个标志成员的取值不在以上范围之内,那么该成员在一些操作中可能不会发挥作用。
什么是标志的规范化标志成员?
如上所述,Python 标志中值为二的整数次幂2ⁿ
(n
大于等于0
)的成员,被称为标志的规范化标志成员。
在下面的标志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
非规范化标志成员不会出现在对标志的迭代遍历中
对一个标志进行迭代遍历,你将得到该标志的所有规范化成员,至于已定义的非规范化成员则不会出现。
# 这里不会出现 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 修饰符确保标志成员值的有效性
使用enum
模块的NAMED_FLAGS
和@verify
修饰符,可以确保标志成员值的有效性,比如,定义一个不存在的组合是不可行的。
在使用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]
如何获取标志成员组合中所有的二的整数次幂?
使用enum
模块的show_flag_values
函数,可以获取某个标志成员组合中所有的二的整数次幂2ⁿ
(n
大于等于0
),结果将作为 Python 列表对象返回。
show_flag_values(value)
- value 参数
value
参数是标志成员的组合。
import enum
print(enum.show_flag_values(COLOR.BLACK_AND_WHITE))
[1, 2]
获取 Python 标志中规范化标志成员的个数
从Flag
或IntFlag
继承的标志,可以使用函数len
获取规范化成员的个数。
标志COLOR
的成员NONE
的值为0
,成员BLACK_AND_WHITE
的值为3
,因此他们不会被计算。
# COLOR 中的 NONE 和 BLACK_AND_WHITE 不会被计算
print(f'COLOR 规范化成员个数:{len(COLOR)}')
COLOR 规范化成员个数:5
组合 Python 标志成员
从Flag
或IntFlag
继承的标志,可以使用或位运算符|
,来组合标志中的多个标志(枚举)成员。
# 组合成员 WHITE,BLACK,RED
color = COLOR.WHITE | COLOR.BLACK | COLOR.RED
迭代遍历 Python 标志成员组合中的规范化标志成员
通过for
语句,你可以遍历标志成员组合中所包含的规范化成员,非规范化成员不会出现在循环中。
# 并不会显示非规范化成员 BLACK_AND_WHITE
for i in COLOR.BLACK_AND_WHITE | COLOR.GREEN:
print(i)
COLOR.WHITE
COLOR.BLACK
COLOR.GREEN
判断 Python 标志成员是否包含在标志成员组合中
从Flag
或IntFlag
继承的标志,可以使用in
运算符或与位运算符&
,来判断组合中是否包含指定的一个或多个标志(枚举)成员。
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 标志成员组合中规范化标志成员的个数
对于某个标志成员组合,函数len
可以返回该组合中规范化标志成员的个数。
# color 是 WHITE,BLACK,RED 的组合
print(f'color 组合中规范化成员的个数为:{len(color)}')
color 组合中规范化成员的个数为:3
判断 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
继承的标志,可以使用异或位运算符^
,将一个或多个标志成员,从标志成员组合中移除。如果需要移除的标志成员在组合中不存在,那么该标志成员将被添加。
# 从 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
继承的标志,可以使用取反位运算符~
,来获取除指定标志成员之外的所有规范化标志成员,如果找不到符合要求的规范化成员,那么将返回值为0
的成员。
标志的取反运算结果仅包含已定义的规范化标志成员
与其他编程语言不同,标志的取反运算结果,仅包含已定义的规范化标志成员,不会包含任何未定义的标志成员。
# 获取 WHITE,BLACK,RED 以外的成员
print(~color)
# 获取其他所有成员
print(~COLOR.NONE)
COLOR.BLUE|GREEN
COLOR.WHITE|BLACK|RED|BLUE|GREEN
超出取值范围的 Python 标志成员
每一个标志成员都是一个标志(枚举)的实例,如果在创建实例的过程中,指定了超出范围的值,比如,包含了不存在的规范化标志成员,那么 Python 需要进行相应的处理。
你可以在定义标志时,指定boundary
参数,该参数将决定如何处理超出取值范围的标志成员,他是enum
模块的以下变量之一。
- STRICT 变量
STRICT
变量表示,如果标志成员的取值超出范围,那么将引发异常,这是Flag
的默认设置。- CONFORM 变量
CONFORM
变量表示,如果标志成员的取值超出范围,那么移除值所包含的无效成员,从而获得一个有效的新值。- EJECT 变量
EJECT
变量表示,如果标志成员的取值超出范围,那么该成员将被转换为一个整数。- KEEP 变量
KEEP
变量表示,如果标志成员的取值超出范围,那么保留值所包含的无效成员,这是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
是标志类Flag
的派生类,IntFlag
基类包括Flag
和int
,因此IntFlag
可以与其他数字进行算术运算,有效比较,或出现在任何允许使用整数的位置。当参与算术运算时,运算结果将不再是标志成员。
class INTCOLOR(IntFlag):
WHITE = auto()
BLACK = auto()
# 参与比较
print(INTCOLOR.WHITE > 0)
# 参与算术运算,结果将不再是一个标志成员
print(type(INTCOLOR.WHITE + INTCOLOR.BLACK))
True
<class 'int'>