Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
525 views
in Technique[技术] by (71.8m points)

python - Representation of all values in Flag enum

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are a few ways to overcome this issue:


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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...