属性描述符通过__get__、__set__、__delete__方法接管属性访问,需作为类属性使用;数据描述符(含__set__)优先级高于实例字典,非数据描述符仅在实例字典未命中时触发。

属性描述符的核心是__get__、__set__、__delete__
描述符是一个定义了其中至少一个特殊方法的类:__get__(self, obj, objtype=None)、__set__(self, obj, value) 或 __delete__(self, obj)。当这个类的实例被赋值给另一个类的类属性时,它就“激活”为描述符,接管对该属性的访问逻辑。
关键点:
- 只有当描述符实例作为类属性(而非实例属性)定义时,协议才生效
- __get__ 在读取属性时触发;有 __set__ 就是数据描述符,优先级高于实例字典;没有 __set__ 是非数据描述符,优先级低于实例字典
- obj 是调用方实例,objtype 是调用方类;若通过类访问(如 MyClass.attr),obj 为 None
写一个带类型校验的描述符
常见用途是封装字段逻辑,比如限制赋值类型或范围:
class IntField:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
<pre class="brush:php;toolbar:false;">def __set_name__(self, owner, name):
self.name = name # 自动获取属性名,避免手动传参
def __set__(self, obj, value):
if not isinstance(value, int):
raise TypeError(f'{self.name} must be an integer')
if self.min_value is not None and value < self.min_value:
raise ValueError(f'{self.name} must be ≥ {self.min_value}')
if self.max_value is not None and value > self.max_value:
raise ValueError(f'{self.name} must be ≤ {self.max_value}')
obj.__dict__[self.name] = value # 存入实例字典,避免递归
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)class Person: age = IntField(min_value=0, max_value=150) score = IntField(0, 100)
使用时:
立即学习“Python免费学习笔记(深入)”;
- p = Person(); p.age = 25 → 触发 __set__,校验通过
- p.age = -5 → 抛出 ValueError
- Person.age → obj 为 None,返回描述符自身(支持类访问)
__set_name__ 让描述符更易用
Python 3.6+ 提供 __set_name__(self, owner, name),在类创建时自动调用,帮你绑定属性名。否则你得手动写 age = IntField('age'),容易出错且冗余。
注意:
- __set_name__ 只在描述符作为类属性被赋值时调用,不会重复触发
- 它替代了早期用 __init__(name) + 显式传名的写法,代码更干净
- 如果描述符未定义 __set_name__,你仍需自己管理属性名(例如通过 __set__ 中 inspect.stack() 获取,但不推荐)
数据描述符 vs 非数据描述符:影响属性查找顺序
这是理解描述符行为的关键:
- 数据描述符:实现 __set__ 或 __delete__。访问时总是优先触发,即使实例字典里有同名 key
- 非数据描述符:只实现 __get__。仅当实例字典中找不到该属性时才触发;若 obj.__dict__['x'] 存在,就直接返回它,跳过 __get__
举例说明:
class NonDataDesc:
def __get__(self, obj, t):
return 'from descriptor'
<p>class DataDesc:
def <strong>set</strong>(self, obj, v): pass
def <strong>get</strong>(self, obj, t):
return 'from data descriptor'</p><p>class Test:
a = NonDataDesc()
b = DataDesc()</p><p>t = Test()
t.a = 'in instance' # 写入实例字典,a 不再走描述符
t.b = 'in instance' # 走 <strong>set</strong>,不会存进 <strong>dict</strong>
print(t.a) # 'in instance'(非数据描述符被绕过)
print(t.b) # 'from data descriptor'(数据描述符强制拦截)
</p>这个机制是 property、classmethod、staticmethod 的底层基础,也是实现懒加载、缓存、代理等模式的起点。










