5
\$\begingroup\$

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) + '>'
asked Feb 26, 2013 at 21:36
\$\endgroup\$
1
  • \$\begingroup\$ I would argue this isn't a Pythonic interface. Set attributes on an object instead of using flags, or use a dict. \$\endgroup\$ Commented Feb 26, 2013 at 23:23

1 Answer 1

1
\$\begingroup\$

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.

answered Feb 28, 2013 at 11:00
\$\endgroup\$
1
  • \$\begingroup\$ (x*2 for x in mylist) is the same as much more verbose def double(mylist): for x in mylist: yield x*2; and then referencing double(mylist) to use it. I don't think that syntax sugar is bad in and of itself. \$\endgroup\$ Commented Mar 11, 2013 at 21:52

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.