21

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.

5 Answers 5

12
+100

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.NONE = none_mbr enumeration.ALL = all_mbr 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> 
Sign up to request clarification or add additional context in comments.

7 Comments

@ZeroPiraeus: Yes, you have the reasoning correct. Thanks for catching that. A little more effort (on my part) shows that an ALL value on an empty Enum is non-sensical.
Nice! I've added a note to my answer, redirecting readers to yours. Maybe it's worth adding a guard against empty enums (which ISTM would be best off just omitting to define ALL in that case) to @with_limits, similar to the one in AllFlag ...
Ideally, the decorator will only be applied to a populated Enum (a non-empty _member_names_ is the clue). Whether that situation should raise an error or just decline to modify is something the user would need to decide, and modify the above code appropriately.
Actually, I couldn't get the decorator version to work without adding an enumeration.ALL = ... before the enumeration._member_map_['ALL'] = ... for each member; I was getting AttributeErrors when trying to access them on Python 3.12. (Meanwhile adding the enumeration.ALL = ... assignment after the member map assignment caused it to fail with a "cannot reassign member" error. Go figure.)
@MariusGedminas: Thanks for catching that -- in 3.12 the __getattr__ method for Enum went away, so the explicit assignment became necessary. Answer updated.
|
6

TL;DR Because property is only evaluated on instances of a class while the __members__ is only accessible on the class.


If you access a property on a class it just returns a property:

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

To make this work however you need to either make it a classmethod:

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> 

or access the property on an instance:

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> 

In both cases you can do your comparison later:

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

You stated in the comments that you want the ALL member to behave like the others, in that case I suggest using a class decorator:

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> 

The class decorator can also be used on other enums :)

6 Comments

Thank you, forgot that this was the class itself and not an instance But neither of those yield the RefreshFlags.ALL that should fit in nicely with the other values.
I want a flag value containing the union of all values, the intersection with this "ALL" value should be the original value. it should be used like that: def func(flags): if flags & RefreshFlags.EVENTS:
ok, could you elaborate what you mean by "fit in nicely with the other values" means?
@Sekuraz Ah, now I got it. I've added a class-decorator method that adds an ALL attribute to your enums.
I like the decorator idea (not so much on the others ;) -- changed my vote.
|
6

Following up on MSeifert's answer, it's possible to write a @classproperty decorator which allows you to access RefreshFlags.ALL directly as a property (rather than as a conventional method or a property on an instance):

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) 

You can of course write ALL() with an explicit for loop as in your example; the above is merely offered as an alternative.

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

6 Comments

Thank you very much for this solution. I am using the for loop because I am not that accustomed to functional programming.
Beat me to it! Include the warning about being able to overwrite ALL and I'll remove my answer.
@EthanFurman Added ... but because you're right and people might not read past the accepted answer, not because I expect you to delete anything. Let a thousand flowers bloom, etc.
Well, having one answer be a complete subset of another seems silly... so I updated mine to be more comprehensive of all our approaches. After doing that it seems that a decorator (mine, to be exact ;) is a more robust way to go in this case. And I found a bug in enum, so thank you to everyone for that!
I would just like to chime in and say that it's possible to simply write RefreshFlags(~0) instead of relying on reduce() or for loops. Perhaps a bit hacky, but shorter and obviates the need for extra imports.
|
4

Here is a slightly more compact solution, using a slimmer descriptor which makes it easier to reuse.

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> 

As already pointed out, a member defined this way doesn't get to be included in the namespace's _member_map_ dictionary, therefore it is not protected against overwriting it.

Comments

0

An easy way to get all Flags is ~RefreshFlags(0) which gives RefreshFlags.EVENTS|RESOURCES|BUILDINGS|DEFENSES| (note sorted in declaration order).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.