Python:如何为@classmethod修饰的@property添加@property.setter
问题描述
这是问题《Python: how to sum/multiply an int with the value of a @property》的后续提问。此前为了对关联类属性的@property值进行算术运算,将该属性定义为@classmethod。现在希望修改这个属性,示例代码中的changeAtt方法可实现需求,但尝试为att添加@property.setter时,出现错误:AttributeError: 'classmethod' object has no attribute 'setter',调换装饰器顺序也无效,请问问题出在哪里?
示例代码
class A: __att = 3 @classmethod @property def att(cls): return cls.__att """ @classmethod @att.setter def att(cls, val): cls.__att = val """ @classmethod def changeAtt(cls, val): cls.__att = val def test_method(self): return 3*A.att a = A() a.att = 5 #adding "att" to instance print(a.test_method()) a.changeAtt(5) print(a.test_method())
输出
9 15
问题原因与解决方案
问题根源
Python原生不支持@classmethod与@property的组合使用setter,核心原因:
@property是为实例属性设计的装饰器,其setter逻辑绑定在实例属性操作上;@classmethod装饰后的对象是类方法类型,本身没有setter属性,强行组合会触发装饰器逻辑冲突,导致报错。
另外需要注意:示例中a.att = 5并不是修改类属性__att,而是给实例a新增了一个名为att的实例属性,所以test_method里调用A.att仍然取类的原始值3,输出9;而changeAtt是直接修改类属性,因此第二次输出15。
解决方案
方案1:用实例@property操作类属性
如果可以通过实例访问并修改类属性,不需要直接用A.att的类级别访问方式,可去掉@classmethod,在property的getter和setter里操作self.__class__.__att:
class A: __att = 3 @property def att(self): return self.__class__.__att @att.setter def att(self, val): self.__class__.__att = val def test_method(self): return 3 * self.att a = A() a.att = 5 print(a.test_method()) # 输出15 b = A() print(b.att) # 输出5,类属性已被修改
方案2:自定义类级别的property装饰器
如果需要支持A.att这种类级别直接访问和修改,可以自定义一个类级别的属性装饰器,模拟@property的逻辑但作用于类:
class classproperty: def __init__(self, fget=None, fset=None): self.fget = fget self.fset = fset def __get__(self, instance, owner): return self.fget(owner) def __set__(self, instance, value): self.fset(instance.__class__, value) def setter(self, fset): return type(self)(self.fget, fset) class A: __att = 3 @classproperty def att(cls): return cls.__att @att.setter def att(cls, val): cls.__att = val def test_method(self): return 3 * A.att a = A() a.att = 5 print(a.test_method()) # 输出15 print(A.att) # 输出5,类属性已被修改
这个自定义的classproperty实现了类级别的属性访问和设置,用法和原生@property一致,同时支持类和实例两种方式操作属性。
内容的提问来源于stack exchange,提问作者massi

