






















描述符协议(Descriptor Protocol)是 Python 中一个核心且强大的底层机制。它允许你自定义对象属性的访问、赋值和删除行为。
简单来说,描述符就是实现了特定协议方法的 Python 对象,它作为一种“可重用的属性逻辑”存在,可以让你优雅地管理属性。
我们日常使用的许多 Python 高级特性,如 @property、@classmethod、@staticmethod,甚至是函数绑定机制,其底层都是由描述符协议实现的。
一个对象要成为描述符,需要实现以下一个或多个方法:
| 方法 | 签名 | 触发时机 |
|---|---|---|
__get__ |
__get__(self, instance, owner) |
读取属性时 (如 obj.attr) |
__set__ |
__set__(self, instance, value) |
赋值属性时 (如 obj.attr = value) |
__delete__ |
__delete__(self, instance) |
删除属性时 (如 del obj.attr) |
__set_name__ |
__set_name__(self, owner, name) |
(Python 3.6+) 类创建时自动调用,告知描述符其被赋值的属性名 |
这些方法的参数含义如下:
self: 描述符实例本身。instance: 拥有该属性的实例对象(通过类访问时为 None)。owner: 拥有该属性的类。value: 要赋的新值。重要前提:描述符协议只有在描述符对象被作为类属性(即定义在类体中)时才会生效。如果将其赋值给某个实例,则它只是一个普通对象,协议方法不会被触发。
根据实现的方法,描述符分为两类,它们在属性查找时的优先级不同:
__get__ 和 __set__ 方法。它的优先级最高,会覆盖实例属性(instance.__dict__)。__get__ 方法。它的优先级较低,只有在实例属性(instance.__dict__)中找不到对应属性时才会被触发。这个优先级区别是理解 Python 属性查找机制的关键。
当你访问一个对象的属性(如 obj.attr)时,Python 遵循以下查找顺序:
obj 的类及其父类中查找 attr。如果它是一个数据描述符,则直接调用其 __get__ 方法并返回,查找结束。obj.__dict__ 字典。如果找到 attr,则直接返回其值,查找结束。obj 的类及其父类中查找 attr。如果它是一个非数据描述符,则调用其 __get__ 方法并返回。__getattr__:如果以上都未找到,且类中定义了 __getattr__ 方法,则调用它。AttributeError:最终仍未找到,则抛出 AttributeError。描述符在 Python 生态中被广泛应用:
models.Field 和 SQLAlchemy 的列类型,都是描述符。@property:将方法调用变成属性访问。@classmethod 和 @staticmethod:改变方法的调用方式。下面这个例子展示了如何使用描述符创建一个支持类型检查的属性:
class TypedProperty:
def __init__(self, expected_type):
self.expected_type = expected_type
def __set_name__(self, owner, name):
# 获取属性在类中的名字,例如 'name' 或 'age'
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
# 从实例的 __dict__ 中获取值
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
# 在赋值时进行类型检查
if not isinstance(value, self.expected_type):
raise TypeError(f"Expected {self.expected_type.__name__}, got {type(value).__name__}")
# 验证通过,将值存入实例的 __dict__
instance.__dict__[self.name] = value
class Person:
# 将描述符作为类属性使用
name = TypedProperty(str)
age = TypedProperty(int)
def __init__(self, name, age):
self.name = name
self.age = age
现在,Person 类的属性拥有了类型检查的能力:
>>> p = Person("Alice", 30)
>>> p.name
'Alice'
>>> p.age
30
>>> p.name = 123 # 尝试赋一个非字符串值
Traceback (most recent call last):
...
TypeError: Expected str, got int
在这个例子中,TypedProperty 类实现了描述符协议。__set_name__ 让它知道自己在 Person 类中的属性名,__get__ 和 __set__ 则接管了对该属性的读写操作,并在写入时加入了类型校验逻辑。
描述符协议是理解 Python 底层运作和进行高级编程的利器,它让你能够以优雅、可复用的方式控制属性行为。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。