Python中使用property时Class.foo与instance.foo的行为差异
先重现你给出的两个案例代码:
案例1:使用property
class Case1: # 忽略property的getter和setter var = property(getter, setter, None, None)
案例2:使用等效方法
class Descriptor: def __get__(self, obj, type=None): return None def __set__(self, obj, val): pass class Case2: var = Descriptor()
这其实是Python描述符协议的核心逻辑在起作用,咱们把这个现象拆解开说:
首先要明确:property本质上是一个实现了描述符协议的类,它属于「数据描述符」(因为同时实现了__get__和__set__方法)。而描述符的调用行为,会根据你是通过实例访问还是类访问,产生完全不同的结果:
当通过实例访问属性时:
Python会先检查实例的__dict__里有没有这个属性,如果没有,就去类的属性中查找。如果找到的是一个描述符(实现了__get__),就会调用这个描述符的__get__方法,并且把当前实例作为第一个参数obj传入。
对于property来说,当obj不为None时,它会调用你绑定的getter函数,返回对应属性的实际值——这就是你看到instance.var返回实际值的原因。当通过类访问属性时:
同样会找到类属性里的描述符,但这时候调用__get__方法时,第一个参数obj会被传入None。而property的__get__方法做了特殊处理:当obj是None时,它会直接返回property对象本身,而不是去调用getter。
这是Python的刻意设计——目的是让你可以直接获取到描述符对象本身,而不是触发它的属性获取逻辑。
对比你案例2里的自定义Descriptor,它的__get__方法没有处理obj为None的情况,所以不管是类访问还是实例访问,都会返回None。如果给它加上类似property的逻辑:
class Descriptor: def __get__(self, obj, type=None): if obj is None: return self # 类访问时返回自身 return None def __set__(self, obj, val): pass
这时候你再访问Case2.var,就会返回Descriptor对象本身,和property的表现一致了。
总结一下核心点:
- property的
__get__方法对「类访问」和「实例访问」做了分支处理; - 类访问时返回自身,方便你操作描述符对象;
- 实例访问时触发getter逻辑,返回实际属性值。
内容的提问来源于stack exchange,提问作者Sheng Wang

