我一直在寻找一种在Python中定义类属性的方法。
预期的行为将是直观的:
class A:
_access_count = 0
@classproperty
def value1(cls):
cls._access_count += 1
return 1
A.value1 # return 1
A().value1 # return 1
A._access_count # return 2
A.value1 = 2 # raise an AttributeError
我在SO上找到了相关问题,但没有人提出这个确切的功能。
This thread有一个很好的元类示例,即使它在这种情况下并不真正适用。接受的this one答案提出了一个紧密的解决方案,但没有处理制定者的机制。
答案 0 :(得分:0)
因为它是ok to answer his own question我会写出我到目前为止所写的内容。
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__))
到目前为止,它完全按预期工作,即使对于继承情况:
@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))