python-3.xenumsboolean-operationsenum-flags

Is it possible to make a class which lets you stack enum Flags?


I'd like to use named constants whereever possible instead of providing literal values or longish function signatures with a lot of boolean args.

Therefore i like pythons enum.Flag or enum.Enum.

More precisely, I would like to pass an argument to a function which holds a bit combination of enum.Flags. And i would like to avoid writing module.TheFlags.flagX for every set flag I would like to pass to the function. The flags should replace the boolean args.

I came up with following code:

import enum


class AvailableFlags(enum.Flag):
    flag1 = enum.auto()
    flag2 = enum.auto()


class FuncFlags:
    def __init__(self):
        self._flags = AvailableFlags(0)

    @property
    def flag1(self):
        self._flags |= AvailableFlags.flag1
        return self

    @property
    def flag2(self):
        self._flags |= AvailableFlags.flag2
        return self

    def __str__(self):
        return str(self._flags.value)


def func(setup_flags: FuncFlags):
    print(setup_flags)


if __name__ == "__main__":
    func(FuncFlags().flag1)
    func(FuncFlags().flag2)
    func(FuncFlags().flag1.flag2)
    func(FuncFlags())

It creates instances of FuncFlags and then mis-uses the properties to set single flags returning the changed object itself. However, one would expect that the property does NOT change object state. Therefore, this is obviously not a clean solution despite that it works, though.

So, my question is, how this can be implemented in a clean, reusable way?


Solution

  • Meanwhile, I found an answer by adding another level of indirection. I want to share it here if it is of interest for someone else. Object state is maintained as every invokation of a flag creates a new instance from the current instance by setting an additional flag. If we attempt to access an undefined flag an exception is raised (not shown).

    import enum
    
    
    class AvailableFlags(enum.Flag):
        flag1 = enum.auto()
        flag2 = enum.auto()
    
    
    class FlagHelper:
        def __init__(self, cls, value = 0):
            self._cls = cls
            self._flags = self._cls(value)
    
        def __getattr__(self, item):
            if item in self._cls.__members__:
                return self.__class__(self._flags | getattr(self._cls, item))
    
            getattr(self._cls, item)  # Let attribute error pass through
    
        def __str__(self):
            return str(self._flags.value)
    
    
    class FuncFlags(FlagHelper):
        def __init__(self, value = 0):
            super().__init__(AvailableFlags, value)
    
    
    def func(setup_flags: FuncFlags):
        print(setup_flags)
    
    
    if __name__ == "__main__":
        ff = FuncFlags()
        func(ff.flag1)
        func(ff.flag2)
        func(ff.flag1.flag2)
        func(ff)
    

    Output:

    1
    2
    3
    0