如何在Python中正确定义类属性

时间:2013-12-20 14:04:48

标签: python

我一直在寻找一种在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答案提出了一个紧密的解决方案,但没有处理制定者的机制。

1 个答案:

答案 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))