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) + '>'
1 Answer 1
One real-world use-case might be to pass such "flags" values to some ffi code, where these are used for efficiency, but in python it seem inefficient (as each instance basically introduces a dict plus an object struct) and much less readable.
Imagine encountering such lines in someone's code - now how would you update some flags? list them? reset all?
If it's not known, I think it won't be intuitive (contrast with facing regular dict with True/False values), and will require person to go dig into these classes.
And while example like
class Name:
InsertRead = True
InsertWrite = True
InsertRequired = True
UpdateRead = True
UpdateRequired = True
UpdateWrite = False
Might be indeed overly verbose for someone who wrote it, I'd say it should be expanded with comments about what each of these flags means, especially if you're going to pass this structure around:
class Name:
#: Perform database INSERT on read/write manipulations.
InsertRead = True
InsertWrite = True
#: Will rollback the transaction with IntegrityError
#: if INSERT was not performed (object with same value
#: was already present).
InsertRequired = True
...
As a bonus, you get crucial "no surprises" behavior, which I think other developers will appreciate, having to spend no time figuring out how to work with the thing.
Another bonus - run Sphinx over it and get a good and readable API reference.
Basically, I think it's cleverness for it's own sake.
Sure, one can write custom language for some specific task on top of python, but place yourself in the shoes of someone coming at this with python knowledge only and it'd be hard to justify doing so.
-
\$\begingroup\$
(x*2 for x in mylist)
is the same as much more verbosedef double(mylist): for x in mylist: yield x*2;
and then referencingdouble(mylist)
to use it. I don't think that syntax sugar is bad in and of itself. \$\endgroup\$gahooa– gahooa2013年03月11日 21:52:31 +00:00Commented Mar 11, 2013 at 21:52
dict
. \$\endgroup\$