Python 属性描述符 descriptor 原理

1次阅读

python属性描述符本质是通过__get__/__set__/__delete__方法控制属性访问,依据查找顺序(类属性优先于实例属性)触发;数据描述符(含__set__)覆盖实例属性,非数据描述符(仅__get__)可被实例属性遮蔽。

Python 属性描述符 descriptor 原理

Python 属性描述符(descriptor)的本质,是通过类的特殊方法(__get____set____delete__)控制对实例属性的访问逻辑。它不是语法糖,而是 Python 属性查找机制中明确参与的一环——当访问某个属性时,如果该属性是一个实现了至少一个描述符方法的对象,且被定义在类的命名空间中(而非实例字典里),解释器就会触发对应的方法。

描述符为什么能接管属性访问

关键在于 Python 的属性查找顺序(MRO 中的类属性优先于实例属性)。当执行 obj.attr 时,解释器按如下逻辑处理:

  • 先在 obj.__class__.__dict__ 中查找 attr
  • 如果找到且该对象有 __get__ 方法(即它是“非数据描述符”或“数据描述符”),就调用 attr.__get__(obj, type(obj))
  • 如果是“数据描述符”(同时定义了 __set____delete__),它会**覆盖**实例字典中的同名项(即 obj.__dict__['attr'] 完全无效)
  • 只有当类中没找到描述符,才会去查 obj.__dict__

数据描述符 vs 非数据描述符的区别

这个区分直接影响属性行为,是理解 descriptor 的核心:

  • 数据描述符:实现了 __set____delete__(哪怕只实现其中一个);它优先级最高,能屏蔽实例字典里的同名属性
  • 非数据描述符:只实现了 __get__;当实例字典中存在同名 key 时,会直接返回实例值,跳过 __get__

例如:Property 是数据描述符(内部含 __set__),所以 @property 定义的属性不能被实例赋值覆盖;而函数对象是典型的非数据描述符,所以方法调用走的是 func.__get__(obj, cls),但你仍可给实例设 obj.func = 42 来遮蔽它。

立即学习Python免费学习笔记(深入)”;

手动写一个有用的描述符

比如实现一个带类型校验的属性:

class Typed:     def __init__(self, expected_type):         self.expected_type = expected_type         self.name = None  # 后续由 __set_name__ 填充(Python 3.6+) <pre class="brush:php;toolbar:false;">def __set_name__(self, owner, name):     self.name = name  def __set__(self, obj, value):     if not isinstance(value, self.expected_type):         raise TypeError(f'{self.name} must be {self.expected_type.__name__}')     obj.__dict__[self.name] = value  def __get__(self, obj, owner):     if obj is None:         return self     return obj.__dict__.get(self.name)

使用

class Person: name = Typed(str) age = Typed(int)

p = Person() p.name = “Alice” # OK p.age = 30 # OK p.age = “30” # TypeError

注意:__set_name__ 是可选的,用于自动获取属性名,避免手动传参;实际存取仍通过实例字典完成,保证效率。

常见内置描述符和使用场景

很多 Python 特性底层都基于描述符:

  • property:最常用的数据描述符,封装 getter/setter/deleter
  • classmethodstaticmethod:非数据描述符,影响绑定行为(cls / 无绑定)
  • 函数对象本身:也是非数据描述符,所以 obj.method 触发 method.__get__(obj, cls) 返回绑定方法
  • 第三方库如 attrsdataclasses 内部也大量依赖描述符做字段管理

理解描述符,等于看懂了 Python “属性访问”这层抽象背后的执行链路——它不神秘,只是把控制权交给了开发者定义的对象。

text=ZqhQzanResources