I am looking for feedback on this compact Python API for defining bitwise flags and setting/manipulating them.
Under the hood, FlagType extends int and all operations are true bitwise, so there is little to no performance impact. Also, because they are all int, any standard bitwise operation can be performed on them.
Usage Example
>>> class sec(FlagType):
... admin = 1
... read = 2
... write = 4
... usage = 8
...
>>> flags = +sec.read -sec.write +sec.usage
>>> x.read
True
>>> print(flags)
10
>>> repr(flags)
"<sec (0b1010) {'write': False, 'usage': True, 'admin': False, 'read': True}>"
This code arose out of the desire to replace:
class Name(Varchar):
InsertRead = True
InsertWrite = True
InsertRequired = True
UpdateRead = True
UpdateRequired = True
UpdateWrite = False
with:
class Name(Varchar)
flags = +Read +Write +Required -UpdateWrite
More Examples:
class color(FlagType):
red = 0b0001
blue = 0b0010
purple = 0b0011
_default_ = red
color()
:: <color (0b1) {'blue': False, 'purple': False, 'red': True}>
flags = +color.blue
flags
:: # Note the default of red came through
:: # Note that purple is also true because blue and red are set
:: <color (0b11) {'blue': True, 'purple': True, 'red': True}>
flags.blue
:: True
flags.red
:: True
flags -= color.blue
flags.blue
:: False
flags.purple
:: False
flags.red
:: True
flags
:: <color (0b1) {'blue': False, 'purple': False, 'red': True}>
flags[color.red]
:: True
Source Code:
class FlagMeta(type):
def __new__(metacls, name, bases, classdict):
if '_default_' in classdict:
def __new__(cls, value=classdict.get('_default_')):
return int.__new__(cls, value)
del classdict['_default_']
classdict['__new__'] = __new__
cls = type.__new__(metacls, name, bases, classdict)
for flagname,flagvalue in classdict.items():
if flagname.startswith('__'):
continue
setattr(cls, flagname, cls(flagvalue))
return cls
def __setattr__(cls, name, value):
if type(value) is not cls:
raise AttributeError("Attributes of class '{0}' must be instances of '{0}'.".format(cls.__name__))
if type(value) is FlagType:
raise AttributeError("Class '{0}' is read-only.".format(cls.name))
type.__setattr__(cls, name, value)
class FlagType(int, metaclass=FlagMeta):
def __pos__(self):
'''
Creates a new default instance of the same class and then adds the current
value to it.
'''
return type(self)() + self
def __neg__(self):
'''
Creates a new default instance of the same class and then subtracts the current
value from it.
'''
return type(self)() - self
def __add__(self, other):
'''
Adding only works with flags of this class or a more generic (parent) class
'''
if not isinstance(self, type(other)):
raise TypeError("unsupported operand type(s) for {0}: '{1}' and '{2}'".format(('+'), type(self), type(other)))
return type(self)(self | other)
def __sub__(self, other):
'''
Subtracting only works with flags of this class or a more generic (parent) class
'''
if not isinstance(self, type(other)):
raise TypeError("unsupported operand type(s) for {0}: '{1}' and '{2}'".format(('-'), type(self), type(other)))
return type(self)(self & ~other)
def __getattribute__(self, othername):
'''
If the requested attribute starts with __, then just return it.
Otherwise, fetch it and pass it through the self[...] syntax (__getitem__)
'''
if othername.startswith('__'):
return object.__getattribute__(self, othername)
else:
return self[getattr(type(self), othername)]
def __setattr__(self, name, val):
'''
Readonly.
'''
raise AttributeError("'{0}' object is readonly.".format(type(self).__name__))
def __getitem__(self, other):
'''
Passing an instance of this same class (or a parent class) to the item getter[]
syntax will return True if that flag is completely turned on.
'''
if not isinstance(self, type(other)):
raise TypeError("unsupported operand type(s) for {0}: '{1}' and '{2}'".format(('-'), type(self), type(other)))
return self & other == other
def __repr__(self):
'''
For example:
<FieldFlag (0b1001) {'Read': True, 'Required': False, 'Write': True, 'Execute': False}>
'''
fields = {}
for c in type(self).__mro__[0:-2]: #up to but not including (int, object)
for fieldname, fieldvalue in c.__dict__.items():
if not fieldname.startswith('__'):
fields[fieldname] = self[fieldvalue]
return '<' + type(self).__name__ + ' (' + bin(self) + ') ' + str(fields) + '>'
dict. \$\endgroup\$