Reputation: 1418
I want to use an enum.Flag
in a library function to allow users configuring multiple additive options.
Since some of these options are usually what users would want to do, there is a sensible default.
However it should still be possible to pass in a value to disable all options.
Reading up on enum.Flag
and following the advice to "use auto
as the value and let Flag
select an appropriate value" I came up with something like this:
import enum
class Foo(enum.Flag):
NONE = enum.auto()
BAR = enum.auto()
BAZ = enum.auto()
def do_something(foo: Foo = Foo.BAR):
pass
This works but when combining the Foo.NONE
flag with say Foo.BAR
I end up with <Foo.BAR|NONE: 3>
while I'd want to end up with just <Foo.BAR: 2>
.
I also tried not defining the NONE
member at all but this requires users to pass in Foo(0)
if they want to not specify any flags which is not obvious at all.
How do you define this class so that combining Foo.NONE
and Foo.BAR
ends up as Foo.BAR
and users get to pass an obvious member to not set any of the flags?
Upvotes: 4
Views: 600
Reputation: 1418
To someone not used to how bit operations and flags work this might be a bit confusing.
To those who are Foo.NONE | Foo.BAR
resulting in <Foo.BAR|NONE: 3>
(note the 3
) is probably what gives it away.
So what is happening here?
enum.auto()
assigns appropriate values to enum members starting at 1 as explained in its documentation:Instances are replaced with an appropriate value for Enum members. By default, the initial value starts at 1.
The other important bit to understand what's going on is mentioned in the
Flag
documentationIndividual flags should have values that are powers of two (1, 2, 4, 8, …)
Thus, the example is equivalent to defining the members explicitly like this:
import enum
class Foo(enum.Flag):
NONE = 1
BAR = 2
BAZ = 4
The reason for this is more apparent when thinking about the values in binary representation: Foo.NONE = 1 = 0b001
, Foo.BAR = 2 = 0b010
and Foo.BAZ = 4 = 0b100
.
That allows for easily combining flags e.g. Foo.BAR | Foo.BAZ = 2 | 4 = 0b010 | 0b100 = 0b110 = 6
.
Now combining Foo.None
and Foo.BAR
the same way you end up with 3
or its binary representation of 0b011
which is the representation of having the bits for both Foo.NONE
and Foo.BAR
set.
How to solve the issue?
The "trick" is to think about the bit representation of what is being expressed.
Since individual bits being set correspond to enum members, not having any bits set should correspond to the NONE
member i.e.:
import enum
class Foo(enum.Flag):
NONE = 0
BAR = enum.auto()
BAZ = enum.auto()
Users of your library can easily specify "no flags" using Foo.NONE
this way while combining it with "actual" flags now behaves as expected:
>>> foo = Foo.NONE
>>> foo
<Foo.NONE: 0>
>>> foo |= Foo.BAR
>>> foo
<Foo.BAR: 1>
Upvotes: 2