__get__ 方法必须在 instance is none 时返回 self,否则类访问失效;仅实现 __get__ 为非数据描述符,赋值后不再触发;避免共享状态,应使用 weakkeydictionary 等按实例隔离缓存。

__get__ 方法该返回什么才不会出错?
描述符的 <strong>get</strong> 方法被调用时,Python 会传入三个参数:self(描述符实例)、instance(访问该属性的实例对象)、owner(实例所属的类)。关键点在于:当 instance 为 None(即通过类而非实例访问属性),应返回描述符自身,否则可能触发 AttributeError 或静默失效。
- 如果返回
None或固定值而忽略instance状态,类属性访问会错误地返回实例级结果 - 忘记处理
instance is None场景,会导致MyClass.attr返回None而不是描述符对象,后续无法正常工作 - 不要直接返回
self以外的可变对象(如字典),除非你明确控制其生命周期,否则多个实例会共享同一份数据
class NonDataDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self # 必须返回 self,否则类访问失效
return f"from {instance.name}"
什么时候必须同时实现 __set__ 才能用 __get__?
<strong>get</strong> 单独存在时,描述符属于“非数据描述符”(non-data descriptor)。它只在实例字典中没有同名属性时才被触发。一旦你在实例上赋值(比如 obj.attr = 123),该值就会直接写入 obj.<strong>dict</strong>,后续再读取就绕过 <strong>get</strong>。
- 想让
<strong>get</strong>始终生效(比如做动态计算、权限校验),必须加上<strong>set</strong>(哪怕只是抛错),变成“数据描述符” - 只实现
<strong>get</strong>的常见用途是方法绑定、类级别的懒加载(如@classmethod底层就是靠这个机制) - 若误加了空的
<strong>set</strong>(self, instance, value): pass,反而会屏蔽实例字典赋值,导致所有写操作静默失败
class ReadOnlyDescriptor:
def __get__(self, instance, owner):
return instance._value * 2
def __set__(self, instance, value):
raise AttributeError("Read-only attribute")
如何避免 __get__ 中的 self 属性被多个实例污染?
描述符实例是类级别共享的,但它的状态(如 self.cache)会被所有拥有该描述符的实例共用。这不是 bug,而是设计特性——但容易被当成 bug。
- 不要在描述符里存实例相关数据(如
self.last_accessed),除非你用weakref.WeakKeyDictionary映射到具体实例 - 常见错误:在
<strong>get</strong>里缓存结果到self._cache = {...},结果 A 实例改了,B 实例读到脏数据 - 正确做法是把状态存在
instance.<strong>dict</strong>(需确保不冲突)或用外部映射结构
from weakref import WeakKeyDictionary <p>class CachedProperty: def <strong>init</strong>(self, func): self.func = func self.cache = WeakKeyDictionary() # 按实例隔离 def <strong>get</strong>(self, instance, owner): if instance is None: return self if instance not in self.cache: self.cache[instance] = self.func(instance) return self.cache[instance]</p>
__get__ 触发时机和性能陷阱
<strong>get</strong> 在每次属性访问时都执行,包括 hasattr()、getattr() 和点号访问。它不是惰性初始化,也不是装饰器式的一次性计算。
- 频繁调用的属性(如循环体内)若在
<strong>get</strong>中做耗时操作(IO、复杂计算),会显著拖慢性能 -
hasattr(obj, 'attr')也会触发<strong>get</strong>,如果里面抛异常(比如连接数据库失败),hasattr就返回False—— 这常被误认为“属性不存在”,其实是运行时报错 - 不要用
<strong>get</strong>做副作用操作(如发日志、改全局状态),因为调用时机不可控,且可能被优化掉(如某些 IDE 的代码分析)
描述符逻辑越简单越好;复杂逻辑建议移到单独方法里,由描述符委托调用。真正难的是权衡:既要利用描述符的自动触发能力,又要守住边界,不让它变成隐式副作用的温床。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











