Since it is ok to answer his own question I'll write what I've come up with so far.
class classproperty(property):
"""Class property works exactly like property."""
pass
def create_instance_property(cls_prop):
"""Create instance property from class property."""
fget, fset, fdel = None, None, None
if cls_prop.fget is not None :
fget = lambda self: cls_prop.fget(type(self))
if cls_prop.fset is not None :
fset = lambda self, value: cls_prop.fset(type(self), value)
if cls_prop.fdel is not None :
fdel = lambda self: cls_prop.fdel(type(self))
return property(fget, fset, fdel, cls_prop.__doc__)
def init_for_metaclass(cls, name, bases, dct):
"""__init__ method for a metaclass to handle class properties."""
super(type(cls), cls).__init__(name, bases, dct)
for key, prop in dct.items():
if isinstance(prop, classproperty):
setattr(cls, key, create_instance_property(prop))
setattr(type(cls), key, prop)
def handle_class_property(cls):
"""Class decorator to handle class properties."""
name = type(cls).__name__ + "_for_" + cls.__name__
metacls = type(name, (type(cls),), {"__init__": init_for_metaclass})
return metacls(cls.__name__, cls.__bases__, dict(cls.__dict__))
So far it works exactly as expected, even for inheritance cases:
@handle_class_property
class A(object):
_access_count = 0
@classproperty
def value1(cls):
print cls
cls._access_count += 1
return 1
class B(A):
_access_count = 0
@classproperty
def value2(cls):
cls._access_count += 1
return 2
@value2.setter
def value2(cls, value):
print(value)
a = A()
b = B()
assert (a.value1, A.value1) == (1,1)
assert (b.value1, B.value1) == (1,1)
assert (b.value2, B.value2) == (2,2)
assert B._access_count == 4
assert A._access_count == 2
B.value2 = 42 # This should print '42'
try: B.value1 = 42 # This should raise an exception
except AttributeError as e: print(repr(e))