URLhttps://learnscript.net/zh/python/enumerations/flags/
    复制链接转到说明  示例

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

    我被代码海扁署名-非商业-禁演绎
    阅读 9:22·字数 2812·发布 

    前提

    阅读本节的前提是对 Python 枚举以及枚举的概念有所掌握,你可以查看Python 枚举介绍,以及定义和使用枚举编程教程枚举介绍,以及枚举成员的组合,判断,移除来了解相关信息。

    Python 标志

    Python 标志(Flag)是一种特殊的枚举,他允许多个标志(枚举)成员之间的组合操作,其组合的结果依然是一个标志(枚举)成员。

    要在 Python 中使用标志,你需要定义从enum模块的FlagIntFlag继承的派生类。

    标志成员的取值范围是什么?

    在正常情况下,标志成员的值应该是二的整数次幂2ⁿn大于等于0),使用enum模块的auto类,可以轻松实现对2ⁿ的自动计算。标志成员也可以是2ⁿ以外的值,但他必须表示其他标志成员的某种有效组合,比如,0表示空的组合(不包含任何其他成员)。

    如果一个标志成员的取值不在以上范围之内,那么该成员在一些操作中可能不会发挥作用。

    什么是标志的规范化标志成员?

    如上所述,Python 标志中值为二的整数次幂2ⁿn大于等于0)的成员,被称为标志的规范化标志成员。

    在下面的标志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

    非规范化标志成员不会出现在对标志的迭代遍历中

    对一个标志进行迭代遍历,你将得到该标志的所有规范化成员,至于已定义的非规范化成员则不会出现。

    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 修饰符确保标志成员值的有效性

    使用enum模块的NAMED_FLAGS@verify修饰符,可以确保标志成员值的有效性,比如,定义一个不存在的组合是不可行的。

    在使用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]

    如何获取标志成员组合中所有的二的整数次幂?

    使用enum模块的show_flag_values函数,可以获取某个标志成员组合中所有的二的整数次幂2ⁿn大于等于0),结果将作为 Python 列表对象返回。

    show_flag_values(value)

    value 参数

    value参数是标志成员的组合。

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

    获取 Python 标志中规范化标志成员的个数

    FlagIntFlag继承的标志,可以使用函数len获取规范化成员的个数。

    标志COLOR的成员NONE的值为0,成员BLACK_AND_WHITE的值为3,因此他们不会被计算。

    flags.py
    # COLOR 中的 NONE 和 BLACK_AND_WHITE 不会被计算
    print(f'COLOR 规范化成员个数:{len(COLOR)}')
    COLOR 规范化成员个数:5

    组合 Python 标志成员

    FlagIntFlag继承的标志,可以使用或位运算符|,来组合标志中的多个标志(枚举)成员。

    flags.py
    # 组合成员 WHITE,BLACK,RED
    color = COLOR.WHITE | COLOR.BLACK | COLOR.RED

    迭代遍历 Python 标志成员组合中的规范化标志成员

    通过for语句,你可以遍历标志成员组合中所包含的规范化成员,非规范化成员不会出现在循环中。

    flags.py
    # 并不会显示非规范化成员 BLACK_AND_WHITE
    for i in COLOR.BLACK_AND_WHITE | COLOR.GREEN:
    	print(i)
    COLOR.WHITE
    COLOR.BLACK
    COLOR.GREEN

    判断 Python 标志成员是否包含在标志成员组合中

    FlagIntFlag继承的标志,可以使用in运算符或与位运算符&,来判断组合中是否包含指定的一个或多个标志(枚举)成员。

    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 标志成员组合中规范化标志成员的个数

    对于某个标志成员组合,函数len可以返回该组合中规范化标志成员的个数。

    flags.py
    # color 是 WHITE,BLACK,RED 的组合
    print(f'color 组合中规范化成员的个数为:{len(color)}')
    color 组合中规范化成员的个数为:3

    判断 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继承的标志,可以使用异或位运算符^,将一个或多个标志成员,从标志成员组合中移除。如果需要移除的标志成员在组合中不存在,那么该标志成员将被添加。

    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继承的标志,可以使用取反位运算符~,来获取除指定标志成员之外的所有规范化标志成员,如果找不到符合要求的规范化成员,那么将返回值为0的成员。

    标志的取反运算结果仅包含已定义的规范化标志成员

    与其他编程语言不同,标志的取反运算结果,仅包含已定义的规范化标志成员,不会包含任何未定义的标志成员。

    flags.py
    # 获取 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是没有问题的,他表示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是标志类Flag的派生类,IntFlag基类包括Flagint,因此IntFlag可以与其他数字进行算术运算,有效比较,或出现在任何允许使用整数的位置。当参与算术运算时,运算结果将不再是标志成员。

    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