(global) Class properties

Bjorn Pettersen BPettersen at NAREX.com
Wed Mar 12 20:09:59 EST 2003


Thought I'd share my attempt at defining class properties that behaved
the way I think they should <wink>. I was hoping to create a class
property, such that the following definitions:

   class C(object):
       _cp = 0
       ...
       cp = classproperty(getcp, setcp)
   
   class D(C):
       def __init__(self, v):
           self.cp = v
       
would produce the following results:

   C.cp = 3    
   print C.cp    # 3
   print D.cp    # 3
   D.cp = 42
   print C.cp    # 42
   d = D(500)
   print C.cp    # 500

i.e. the property should be shared amongst the defining class, all its
subclasses, and instances of both.

There were a couple of problems:

 - a class' properties have to be defined in the metaclass, otherwise
   the descriptor's __set__ method is never called.
 - If you're saying "cp = property(getcp, setcp)" in the metaclass, the
   accessors must also reside in the metaclass.
 - Implementing "setcp(cls,val)" as "cls.__cp = val", will create a new
   _cp variable in each subclass.
 - Things defined in the metaclass, are generally not available to the
   instances of the class...
 - ... and even if they were "self.cp = val" in an instance would create
   a new cp variable in self, unless there was a property cp defined in
   the class.

After a little tinkering I came up with the following (it puts a
placeholder in the class, which the metaclass catches and from it
creates a similarily named property in both the class and the
metaclass):

  class classproperty(object):
      """Simple container that also provides a type to test for."""
      def __init__(self, fget=None, fset=None, fdel=None, doc=None):
          self.accessors = fget, fset, fdel, doc
  
  class cprop_meta(type):
      def __new__(cls, name, bases, clsdict):
          """Converts all classproperty objects into two property
objects,
             one in this class (the metaclass), and one in the class
itself
             (so instances can access it). Also turns all accessor
functions
             into classmethods, which is uneccessary, but convenient
magic.
          """
          
          newdict = {}
          cmethods = []
          for variable, obj in clsdict.items():
              if isinstance(obj, classproperty):
                  cmethods += filter(callable, obj.accessors)
                  # for classes, the property must be in the metaclass
                  setattr(cprop_meta, variable,
property(*obj.accessors))
                  # for instances, the property must be in the class
                  newdict[variable] = property(*obj.accessors)
              else:
                  newdict[variable] = obj
  
          for fn in cmethods:
              name = fn.func_code.co_name
              newdict[name] = classmethod(fn)
  
          return type.__new__(cls, name, bases, newdict)
  
  class C(object):
      __metaclass__ = cprop_meta
      _cp = 0
      def getcp(cls): return C._cp
      def setcp(cls, val): C._cp = val
      cp = classproperty(getcp, setcp)
  
  class D(C):
      def __init__(self, v):
          self.cp = v

which seem to work:
      
>>> C.cp = 3
>>> print C.cp
3
>>> print D.cp
3
>>> D.cp = 42
>>> print C.cp
42
>>> d = D(500)
>>> print C.cp
500
>>> print map(id, [C.cp, D.cp, d.cp])
[9107920, 9107920, 9107920]

Of course, if anyone knows of a simpler approach I'm all ears :-)

-- bjorn





More information about the Python-list mailing list