Python 描述符是什么?数据描述符和非数据描述符优先级怎么排?
描述符是实现了 __get__、__set__、__delete__ 中任意一个的类,被赋值给另一个类的类属性后,会拦截那个属性的访问。Python 的属性查找有一套隐藏规则:当解释器在类(及其 MRO)的 __dict__ 里找到的值是描述符时,不会直接返回它,而是调用描述符的 __get__ 方法。这就是 property、classmethod、staticmethod 的底层原理——它们都是描述符。
追问
数据描述符和非数据描述符有什么区别?优先级怎么排?
关键区别是有没有 __set__。实现了 __get__ + __set__ 的叫数据描述符,只有 __get__ 的叫非数据描述符。优先级:数据描述符 > 实例 __dict__ > 非数据描述符。换句话说,数据描述符能拦截赋值操作,实例 __dict__ 里写不进去;非数据描述符拦截不了,一旦实例 __dict__ 有了同名 key 就被覆盖了。这就是为什么 property(数据描述符)设了 setter 后 obj.x = 1 一定走 setter,而普通方法(非数据描述符)可以被实例属性遮蔽。
Python 属性查找的完整顺序是什么?
按这个顺序:1. 类及其 MRO 的 __dict__ 里找,如果是数据描述符就调 __get__ 返回;2. 实例 __dict__ 里找;3. 回到类的 __dict__,如果是非数据描述符就调 __get__ 返回。这解释了一个经典面试题:为什么实例能覆盖普通方法但覆盖不了 property?因为方法是非数据描述符,步骤 2 的实例 __dict__ 优先级更高;property 是数据描述符,步骤 1 就截走了。
set_name 是什么?为什么需要它?
Python 3.6 新增的钩子。描述符被赋值到类属性时,解释器自动调用 desc.__set_name__(owner, name),把属性名传进去。之前描述符不知道自己叫什么名字,要么手动传(age = Typed('age', int)),要么用元类扫描类 __dict__ 来推断。有了 __set_name__,Django ORM 的 name = CharField() 就不用重复写字段名了——CharField.__set_name__ 会自动收到 'name'。
描述符里怎么存值?为什么不能直接用 self.xxx?
描述符实例是类级别的,所有实例共享同一个描述符对象。如果你在 __set__ 里写 self.value = val,所有实例共享同一个 value,后面的赋值会覆盖前面的。正确做法是存到 obj.__dict__[self.name] 里,或者用 weakref.WeakKeyDictionary 做 self → value 的映射。前一种更常见(property 就这么做的),后一种适合描述符本身需要维护额外状态的场景。
写段代码
python# 用 __set_name__ 实现类型检查描述符 class Typed: def __init__(self, expected_type): self.expected_type = expected_type def __set_name__(self, owner, name): self.name = name # 自动获取属性名 self.storage = f'_{name}' def __get__(self, obj, objtype=None): if obj is None: return self return getattr(obj, self.storage, None) def __set__(self, obj, value): if not isinstance(value, self.expected_type): raise TypeError(f'{self.name} 需要 {self.expected_type.__name__}') setattr(obj, self.storage, value) class User: name = Typed(str) age = Typed(int) u = User() u.name = "Alice" # OK u.age = 25 # OK # u.age = "25" # TypeError: age 需要 int