(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