I would like to have a “ALL” flag in my python Flags enum for which
myenum.EVERY_MEMBER & myenum.ALL == myenum.EVERY_MEMBER
holds true. I currently have:
from enum import Flag, auto class RefreshFlags(Flag): NONE = 0 EVENTS = auto() RESOURCES = auto() BUILDINGS = auto() DEFENSES = auto() .....
Because this enum might grow at any state of development I would like to have something like
@property def ALL(self): retval = self.NONE for member in self.__members__.values(): retval |= member return retval
This does not work:
RefreshFlags.EVENTS & RefreshFlags.ALL TypeError: unsupported operand type(s) for &: 'RefreshFlags' and 'property'
Please note that this question currently only relates to python 3.6 or later.
Advertisement
Answer
There are a few ways to overcome this issue:
use a
classproperty
(seeZero's answer
)use a class decorator (see
MSeifert's answer
)use a mixin (
currently buggy
)create a new base class (see below)
One thing to be aware of with the class property method is since the descriptor is defined on the class and not the metaclass the usual protections against setting and deleting are absent — in other words:
>>> RefreshFlags.ALL <RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15> >>> RefreshFlags.ALL = 'oops' >>> RefreshFlags.ALL 'oops'
Creating a new base 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
And in use:
class RefreshFlag(AllFlag): EVENTS = auto() RESOURCES = auto() BUILDINGS = auto() DEFENSES = auto() >>> RefreshFlag.ALL <RefreshFlag.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>
The interesting difference in the ALL
property is the setting of the name in _member_map_
— this allows the same protections afforded to Enum members:
>>> RefreshFlag.ALL = 9 Traceback (most recent call last): .... AttributeError: Cannot reassign members.
However, there is a race condition here: if RefreshFlag.ALL = ...
occurs before RefreshFlag.ALL
is activated the first time then it is clobbered; for this reason I would use a decorator in this instance, as the decorator will process the Enum before it can be clobbered.
# 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
And in use:
@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>