Python中类与实例上的描述符行为差异技术问询
关于Python描述符__get__方法调用的疑惑解答
嘿,这个问题其实是Python描述符机制里非常典型的场景,我来给你拆解明白~
核心原因:Python属性查找的优先级规则
当你访问一个属性时,Python的查找顺序是有明确优先级的,这直接决定了描述符的__get__方法是否会被调用:
访问类属性时:
类的__dict__中存储的就是你定义的描述符实例,所以Python会直接调用描述符的__get__方法,此时方法的instance参数为None,owner参数是类本身。访问实例属性时:
Python会先检查实例自己的__dict__字典:- 如果实例的
__dict__里已经存在该属性的键(比如你之前给实例赋值过这个属性),就直接返回对应的值,完全不会去触发类中描述符的__get__方法; - 只有当实例的
__dict__里找不到这个键时,才会去类的__dict__中查找,发现是描述符后,才会调用它的__get__方法。
- 如果实例的
用代码直观演示
我们来写个简单的例子验证这个逻辑:
# 定义一个非数据描述符(仅实现__get__) class MyDescriptor: def __get__(self, instance, owner): print(f"触发__get__!instance: {instance}, owner: {owner}") return "来自描述符的返回值" class DemoClass: # 把描述符绑定为类属性 demo_attr = MyDescriptor() # 访问类属性:触发__get__ print("访问类属性:") print(DemoClass.demo_attr) # 输出: # 触发__get__!instance: None, owner: <class '__main__.DemoClass'> # 来自描述符的返回值 # 创建实例 obj = DemoClass() # 第一次访问实例属性:实例__dict__中没有demo_attr,触发__get__ print("\n第一次访问实例属性:") print(obj.demo_attr) # 输出: # 触发__get__!instance: <__main__.DemoClass object at 0x...>, owner: <class '__main__.DemoClass'> # 来自描述符的返回值 # 给实例自己赋值demo_attr obj.demo_attr = "实例自己的属性值" # 再次访问实例属性:直接返回实例__dict__中的值,不触发__get__ print("\n赋值后访问实例属性:") print(obj.demo_attr) # 输出:实例自己的属性值
如何让实例访问始终触发__get__?
如果你希望不管实例有没有赋值,访问属性时都触发描述符的__get__,可以把描述符改成数据描述符——也就是同时实现__get__和__set__方法。数据描述符的优先级高于实例的__dict__,会覆盖实例中的同名属性:
# 定义数据描述符(同时实现__get__和__set__) class MyDataDescriptor: def __get__(self, instance, owner): print(f"触发__get__!instance: {instance}, owner: {owner}") # 这里可以从实例的私有属性中取值 return instance.__dict__.get("_demo_attr", "默认值") def __set__(self, instance, value): print(f"触发__set__!instance: {instance}, value: {value}") # 把值存在实例的私有属性中,避免和描述符名称冲突 instance.__dict__["_demo_attr"] = value class DemoClass2: demo_attr = MyDataDescriptor() obj2 = DemoClass2() # 访问实例属性:触发__get__ print("\n访问数据描述符的实例属性:") print(obj2.demo_attr) # 输出: # 触发__get__!instance: <__main__.DemoClass2 object at 0x...>, owner: <class '__main__.DemoClass2'> # 默认值 # 给实例赋值:触发__set__ obj2.demo_attr = "新的属性值" # 输出:触发__set__!instance: <__main__.DemoClass2 object at 0x...>, value: 新的属性值 # 再次访问实例属性:依然触发__get__ print("\n赋值后访问数据描述符的实例属性:") print(obj2.demo_attr) # 输出: # 触发__get__!instance: <__main__.DemoClass2 object at 0x...>, owner: <class '__main__.DemoClass2'> # 新的属性值
总结一下
- 非数据描述符(仅__get__):实例自己的同名属性会覆盖描述符,访问时直接返回实例的值;
- 数据描述符(get + set):优先级高于实例的
__dict__,无论实例是否赋值,访问都会触发描述符的__get__方法。
内容的提问来源于stack exchange,提问作者skgbanga
相关产品推荐
相关产品推荐

