Python Enums as Flags

I’m a big fan of enumerated types. As someone who started there HLL programming life with C++ (my first love 🙂 ) I’ve always enjoyed how we can safely limit a variable to a range of known, named values. No more magic numbers! Also, particularly as a Qt devotee, I love how we an use enumerations like flags and create masks from them which we can then test using bitwise operators. A good example is Qt’s Alignment flags for positioning elements on screen, which might be implemented and used as follows:

#include <iostream>
using namespace std;

namespace Qt {
    enum Alignment {
        AlignLeft     = 0x0001,
        AlignRight    = 0x0002,
        AlignHCenter  = 0x0004,
        AlignJustify  = 0x0008,
        AlignTop      = 0x0020,
        AlignBottom   = 0x0040,
        AlignVCenter  = 0x0080,
        AlignBaseline = 0x0100,
        AlignCenter   = AlignVCenter|AlignHCenter
    };
    inline Alignment operator|(Alignment a, Alignment b){
        return static_cast<Alignment>(static_cast<int>(a)|static_cast<int>(b));
    }
}

int main(int argc, char *argv[]){
    Qt::Alignment align=Qt::AlignLeft;
    align=Qt::AlignTop|Qt::AlignHCenter;

    if(align&Qt::AlignTop) cout << "Align Top" << endl;

    return 0;
}

As a Python evangelist it had long been a disappointment to me that the language had no concept of enumerated types. So much so that I actually went away and designed my own! Thankfully, in Python 3.4 we now have the enum package! Yay! It’s even been back ported to Python<=3.4 as enum34! Unfortunately, this doesn’t provide support for bitwise operations, however we can easily add them. The following example shows how to implement the Alignment flag C++ example from above in Python:

from enum import Enum as _Enum

class EnumMask(object):
    def __init__(self, enum, value):
        self._enum=enum
        self._value=value

    def __and__(self, other):
        assert isinstance(other,self._enum)
        return self._value&other.bwv

    def __or__(self, other):
        assert isinstance(other,self._enum)
        return EnumMask(self._enum, self._value|other.bwv)

    def __repr__(self):
        return "<{} for {}: {}>".format(
            self.__class__.__name__,
            self._enum,
            self._value
        )

class Enum(_Enum):
    @property
    def bwv(self):
        cls=self.__class__
        idx=list(cls.__members__.values()).index(self)
        return 2**idx

    def __or__(self, other):
        return EnumMask(self.__class__, self.bwv|other.bwv)

    def __and__(self, other):
        if isinstance(other, self.__class__):
            return self.bwv&other.bwv
        elif isinstance(other, EnumMask):
            return other&self
        else: raise

if __name__=="__main__":
    class Qt(Enum):
        AlignLeft     = 0x0001
        AlignRight    = 0x0002
        AlignHCenter  = 0x0004
        AlignJustify  = 0x0008
        AlignTop      = 0x0020
        AlignBottom   = 0x0040
        AlignVCenter  = 0x0080
        AlignBaseline = 0x0100
        AlignCenter   = AlignVCenter|AlignHCenter

    align=Qt.AlignLeft
    align=Qt.AlignTop|Qt.AlignHCenter
    if align&Qt.AlignTop: print("Align Top")

This takes advantage of the fact that the enumerations are stored in order (using OrderedDict) so we can always get a unique bitwise value (bwv) for each enumeration element based on its position. EnumMask simply acts a wrapper for combinatorial values of Enums.

Hope this helps out someone else, please feel to ask questions or for more detailed explanations etc.

Share:
Facebook
Twitter
Google+
http://www.gulon.co.uk/2015/05/20/python-enums-as-flags/
RSS
Follow by Email
SHARE

Leave a Reply

Your email address will not be published. Required fields are marked *