Python 3.6 has added Flag and IntFlag which support the usual bit-wise operations. As a bonus, the resulting values from the bit-wise operations are still members of the original flag class, and are singletons [1].
The aenum library also has this addition and is usable back to Python 2.7.
[1] A bug exists in 3.6.0: if the pseudo-flag members are being created in threads then there is no guarantee that you won't end up with duplicates; this is fixed in 3.6.1 (and never existed in aenum).
Python 3.6 has added Flag and IntFlag which support the usual bit-wise operations. As a bonus, the resulting values from the bit-wise operations are still members of the original flag class, and are singletons [1].
The aenum library also has this addition and is usable back to Python 2.7.
[1] A bug exists in 3.6.0: if the pseudo-flag members are being created in threads then there is no guarantee that you won't end up with duplicates; this is fixed in 3.6.1 (and never existed in aenum).
I've recently published an opensource package py-flags that aims this problem. That library has exactly this functionality and its design is heavily influenced by the python3 enum module.
There are debates about whether it is pythonic enough to implement such a flags class because its functionality has huge overlaps with other methods provided by the language (collection of bool variables, sets, objects with bool attributes or dicts with bool items, ...). For this reason I feel a flags class to be too narrow purpose and/or redundant to make its way to the standard library but in some cases it is much better than the previously listed solutions so having a "pip install"-able library can come in handy.
Your example would look like the following using the py-flags module:
from flags import Flags
class NetlistKind(Flags):
Unknown = 0
LatticeNetlist = 1
QuartusNetlist = 2
XSTNetlist = 4
CoreGenNetlist = 8
All = 15
The above things could be tweaked a bit further because a flags class declared with the library automatically provides two "virtual" flags: NetlistKind.no_flags and NetlistKind.all_flags. These make the already declared NetlistKind.Unknown and NetlistKind.All redundant so we could leave them out from the declaration but the problem is that no_flags and all_flags don't match your naming convention. To aid this we declare a flags base class in your project instead of flags.Flags and you will have to use that in the rest of your project:
from flags import Flags
class BaseFlags(Flags):
__no_flags_name__ = 'Unknown'
__all_flags_name__ = 'All'
Based on the previously declared base class that can be subclassed by any of your flags in your project we could change your flag declaration to:
class NetlistKind(BaseFlags):
LatticeNetlist = 1
QuartusNetlist = 2
XSTNetlist = 4
CoreGenNetlist = 8
This way NetlistKind.Unknown is automatically declared with a value of zero. NetlistKind.All is also there and it is automatically the combination of all of your declared flags. It is possible to iterate enum members with/without these virtual flags. You can also declare aliases (flags that have the same value as another previously declared flag).
As an alternative declaration using the "function-call style" (also provided by the standard enum module):
NetlistKind = BaseFlags('NetlistKind', ['LatticeNetlist', 'QuartusNetlist',
'XSTNetlist', 'CoreGenNetlist'])
If a flags class declares some members then it is considered to be final. Trying to subclass it will result in error. It is semantically undesired to allow subclassing a flag class for the purpose of adding new members or change functionality.
Besides this the flags class provides the operators your listed (bool operators, in, iteration, etc...) in a type-safe way. I'm going to finish the README.rst along with a little plumbing on the package interface in the next few days but the basic functionality is already there and tested with quite good coverage.
Automatically adding composite values to a Flag enum
Iterating over `enum.Flag` types ignores zero-valued in Python 3.11
python - Representation of all values in Flag enum - Stack Overflow
Is there a reason why the python3 documentation doesn't advertise that enum Flags can be tested with 'in' and 'not in'?
Videos
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.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>
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 :)
I read the documentation at https://docs.python.org/3/library/enum.html
and in the section about 'Comparisons' they mentioned is and ==, which both don't work if your enum instance is a 'Flag' subclass and you're doing combinations.
ie:
from enum import Flag, auto
class Z(Flag):
A = auto()
B = auto()
C = auto()
r = Z.A
r == Z.A #True
r is Z.A #True
Z.A is r #True
Z.A in r #True
r in Z.A #Truebut...
r = Z.A | Z.B r == Z.A #False r is Z.A #False Z.A is r #False Z.A in r #True Z.B in r #True Z.C in r #False r in Z.A #False r in Z.B #False r in Z.C #False
You'd expect the documentation would put the 'FLAG in enum_var' operation front and center because otherwise it's not very obvious how to use the Flag type, but not one mention of it.
You can also test flags you make yourself to other flags you make. Ex:
r = Z.A | Z.C b = Z.A | Z.B r in b #False b in r #False c = Z.B | Z.A c in b #True b in c #True c = ZA.B | ZA.A | ZA.C c in b #False b in c #True
Alternative #1: You can have the Enum class do the string lookup for you:
def setValue(self, color):
self.value = self.Color[color]
Usage:
q = testClass()
q.setValue('red')
Reference:
- https://docs.python.org/3/library/enum.html#programmatic-access-to-enumeration-members-and-their-attributes
Alternative #2: If there are no conflicts, you can promote the Enum's members to the parent class:
class testClass:
class Color(Enum):
red = 1
blue = 2
green = 3
red = Color.red
blue = Color.blue
green = Color.green
def setValue(self, color):
self.value = color
Usage:
q = testClass()
q.setValue(q.red)
If your Class Color wont be conflicting with anything else in your application, you can move Color class out of testClass.
#!/usr/bin/python3
from enum import Enum
class Color(Enum):
red = 1
blue = 2
green = 3
class testClass:
def __init__(self):
self.value = 0
def setValue(self, Color):
self.value = Color
Then you will be able to use
q = testClass()
q.setValue(Color.b)