Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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) + '>'
share|improve this question
    
I would argue this isn't a Pythonic interface. Set attributes on an object instead of using flags, or use a dict. –  Lattyware Feb 26 '13 at 23:23
add comment

1 Answer

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.

share|improve this answer
    
(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. –  gahooa Mar 11 '13 at 21:52
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.