Flag 枚举中所有值的表示

Representation of all values in Flag enum

我想在我的 python Flags 枚举中有一个 "ALL" 标志,

myenum.EVERY_MEMBER & myenum.ALL == myenum.EVERY_MEMBER

成立。我目前有:

from enum import Flag, auto

class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()
    .....

因为这个枚举可能会在任何开发状态下增长,所以我想要类似的东西

@property
def ALL(self):
    retval = self.NONE
    for member in self.__members__.values():
        retval |= member
    return retval

这不起作用:

RefreshFlags.EVENTS  & RefreshFlags.ALL

TypeError: unsupported operand type(s) for &: 'RefreshFlags' and 'property'

请注意,此问题目前仅与 python 3.6 或更高版本有关。

TL;DR 因为 属性 仅在 class 的实例上评估,而 __members__ 仅在 class.

上可访问

如果您在 class 上访问 property 它只是 returns property:

>>> RefreshFlags.ALL
<property at 0x2a5d93382c8>

然而,要使这项工作有效,您需要将其设为 class 方法:

from enum import Flag, auto

class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    @classmethod
    def ALL(cls):
        retval = self.NONE
        for member in cls.__members__.values():
            retval |= member
        return retval

>>> RefreshFlags.ALL()
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

或在实例上访问 属性:

from enum import Flag, auto

class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    @property
    def ALL(self):
        retval = self.NONE
        # One needs to access .__class__ here!
        for member in self.__class__.__members__.values():
            retval |= member
        return retval

>>> RefreshFlags.EVENTS.ALL
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

在这两种情况下,您可以稍后进行比较:

>>> RefreshFlags.EVENTS & RefreshFlags.EVENTS.ALL
<RefreshFlags.EVENTS: 1>

您在评论中声明您希望 ALL 成员的行为与其他成员一样,在这种情况下我建议使用 class 装饰器:

def with_ALL_member(enumeration):
    retval = enumeration(0)  # in case NONE is not defined
    for name, member in enumeration.__members__.items():
        retval |= member
    enumeration.ALL = retval
    return enumeration

@with_ALL_member
class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

>>> RefreshFlags.EVENTS & RefreshFlags.ALL
<RefreshFlags.EVENTS: 1>

>>> RefreshFlags.DEFENSES & RefreshFlags.ALL
<RefreshFlags.DEFENSES: 8>

class装饰器也可以用在其他枚举上:)

跟进 MSeifert 的 ,可以编写一个 @classproperty 装饰器,它允许您直接访问 RefreshFlags.ALL 作为 属性(而不是作为传统的方法或实例上的 属性):

from enum import Flag, auto
from operator import or_
from functools import reduce


class classproperty:

    def __init__(self, func):
        self._func = func

    def __get__(self, obj, owner):
        return self._func(owner)


class RefreshFlags(Flag):

    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    @classproperty
    def ALL(cls):
        return reduce(or_, cls)

您当然可以像示例中那样使用显式 for 循环编写 ALL();以上仅作为替代方案提供。

>>> RefreshFlags.ALL
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>
>>> RefreshFlags.ALL & RefreshFlags.BUILDINGS
<RefreshFlags.BUILDINGS: 4>

有几种方法可以解决这个问题:

  • 使用 classproperty(参见

  • 使用 class 装饰器(参见

  • 使用混入 (currently buggy)

  • 创建新基地class(见下文)


使用 class 属性 方法需要注意的一件事是因为描述符是在 class 上定义的,而不是 metaclass 通常的保护措施没有设置和删除——换句话说:

>>> RefreshFlags.ALL
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

>>> RefreshFlags.ALL = 'oops'
>>> RefreshFlags.ALL
'oops'

创建新基地class:

# lightly tested
from enum import Flag, auto
from operator import or_ as _or_
from functools import reduce

class AllFlag(Flag):

    @classproperty
    def ALL(cls):
        cls_name = cls.__name__
        if not len(cls):
            raise AttributeError('empty %s does not have an ALL value' % cls_name)
        value = cls(reduce(_or_, cls))
        cls._member_map_['ALL'] = value
        return value

并在使用中:

class RefreshFlag(AllFlag):
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

>>> RefreshFlag.ALL
<RefreshFlag.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

ALL 属性 中有趣的区别是 _member_map_ 中名称的设置——这允许为 Enum 成员提供相同的保护:

>>> RefreshFlag.ALL = 9
Traceback (most recent call last):
  ....
AttributeError: Cannot reassign members.

然而,这里有一个竞争条件:如果 RefreshFlag.ALL = ... 发生 之前 RefreshFlag.ALL 第一次被激活然后它被破坏;出于这个原因,我会在这种情况下使用装饰器,因为装饰器会在 Enum 被破坏之前对其进行处理。

# lightly tested

from enum import Flag, auto
from operator import or_ as _or_
from functools import reduce

def with_limits(enumeration):
    "add NONE and ALL psuedo-members to enumeration"
    none_mbr = enumeration(0)
    all_mbr = enumeration(reduce(_or_, enumeration))
    enumeration._member_map_['NONE'] = none_mbr
    enumeration._member_map_['ALL'] = all_mbr
    return enumeration

并在使用中:

@with_limits
class RefreshFlag(Flag):
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

>>> RefreshFlag.ALL = 99
Traceback (most recent call last):
  ...
AttributeError: Cannot reassign members.

>>> RefreshFlag.ALL 
<RefreshFlag.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

>>> RefreshFlag.NONE
<RefreshFlag.0: 0>

这是一个稍微更紧凑的解决方案,使用更精简的描述符,使其更容易重用。

class _all:
    def __get__(self, instance, cls):
        return ~cls(0)


class RefreshFlags(Flag):
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    ALL = _all()

RefreshFlags.ALL
>>> <RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

如前所述,以这种方式定义的成员不会包含在命名空间的 _member_map_ 字典中, 因此它没有防止被覆盖的保护。