Fynn Becker
Fynn Becker

Reputation: 1418

How to represent "no flags" using enum.Flag?

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

Answers (1)

Fynn Becker
Fynn Becker

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 documentation

Individual 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

Related Questions