如何在子类中移除父类被@property装饰的属性(getter/setter)
首先,咱们来拆解下你遇到的问题:父类A的id是用@property装饰的只读属性(只有getter,没有setter),所以当子类B试图直接self.id = id时,Python会把这个赋值请求转发给父类的property对象,而它没有实现__set__方法,自然就抛出AttributeError: can't set attribute了。
另外你提到的性能问题确实存在——@property本质是descriptor,每次访问都会触发函数调用,相比直接访问实例属性,开销确实会大不少,对于频繁访问的场景确实需要优化。
下面给你几个实用的解决方案,都能让子类B的id变成普通可直接访问的属性:
方案1:在子类中删除父类的id property
直接在子类定义时删除继承来的id property,这样子类实例就可以自由设置id为普通属性:
from datetime import datetime class A(): def __init__(self, num): self._num = num @property def id(self): return self._num * datetime.now().timestamp() class B(A): del id # 删除继承自父类的property def __init__(self, id, num): super().__init__(num) self.id = id # 现在可以直接赋值了 b = B(12345, 67) print(b.id) # 输出12345,不是父类计算的动态值
这个方法最直接,删除后子类的id就是普通实例属性,访问性能和直接取字典值一致,完全绕过了父类的property逻辑。
方案2:直接操作实例的__dict__字典
如果不想修改子类的类属性,也可以在__init__里直接把id写入实例的__dict__,绕过property的拦截:
class B(A): def __init__(self, id, num): super().__init__(num) self.__dict__['id'] = id # 直接操作实例字典,跳过property的检查 b = B(12345, 67) print(b.id) # 输出12345
这种方式不需要修改子类的类定义,直接在实例层面覆盖掉property的访问逻辑,同样能获得普通属性的访问性能。
方案3:重新定义id为普通类属性
在子类中显式定义id为普通类属性,覆盖父类的property:
class B(A): id = None # 用普通类属性覆盖父类的property def __init__(self, id, num): super().__init__(num) self.id = id b = B(12345, 67) print(b.id) # 输出12345
这个方案和方案1效果类似,都是让子类的id脱离property的控制,变成可赋值的普通属性。
性能验证
这三个方案的访问性能基本一致,都和直接访问实例属性(比如self._num)的速度差不多,完全解决了你提到的property带来的5倍性能损耗问题。
内容的提问来源于stack exchange,提问作者wakey

