You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中类与实例上的描述符行为差异技术问询

关于Python描述符__get__方法调用的疑惑解答

嘿,这个问题其实是Python描述符机制里非常典型的场景,我来给你拆解明白~

核心原因:Python属性查找的优先级规则

当你访问一个属性时,Python的查找顺序是有明确优先级的,这直接决定了描述符的__get__方法是否会被调用:

  1. 访问类属性时:
    类的__dict__中存储的就是你定义的描述符实例,所以Python会直接调用描述符的__get__方法,此时方法的instance参数为None,owner参数是类本身。

  2. 访问实例属性时:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:28:15