2891 字
14 分钟

Python 描述器(Descriptor)深度解析:从原理到 ORM 实战 | Python 进阶核心知识

描述器(Descriptor)是 Python 元编程的核心概念之一,是理解 propertyclassmethodstaticmethod 等内置特性的关键。掌握描述器可以让你编写更优雅、更强大的代码。

Python 描述器示意图

本文将深入讲解描述器的完整知识体系:

  • 描述器协议(__get__ / __set__ / __delete__
  • 数据描述器 vs 非数据描述器
  • 属性查找顺序与优先级
  • property / classmethod / staticmethod 原理
  • ORM 字段实现实战
  • 验证器、缓存、懒加载描述器
  • 描述器组合与最佳实践

一、描述器协议#

1.1 描述器定义#

描述器是实现了以下方法中至少一个的对象:

方法签名作用
__get__(self, obj, objtype=None)__get__(self, instance, owner)访问属性时调用
__set__(self, obj, value)__set__(self, instance, value)设置属性时调用
__delete__(self, obj)__delete__(self, instance)删除属性时调用

1.2 最简描述器示例#

class SimpleDescriptor:
"""最简单的描述器示例"""
def __get__(self, instance, owner):
print(f"__get__ 被调用: instance={instance}, owner={owner}")
return self._value if hasattr(self, '_value') else None
def __set__(self, instance, value):
print(f"__set__ 被调用: instance={instance}, value={value}")
self._value = value
class MyClass:
attribute = SimpleDescriptor()
# 使用
obj = MyClass()
obj.attribute = 42 # __set__ 被调用
print(obj.attribute) # __get__ 被调用,输出: 42

1.3 __get__ 参数详解#

class Descriptor:
def __get__(self, instance, owner):
"""
参数说明:
- self: 描述器实例
- instance: 拥有该描述器的类实例(如果是类访问则为 None)
- owner: 拥有该描述器的类
"""
if instance is None:
# 通过类访问时(MyClass.attr)
return self
# 通过实例访问时(obj.attr)
return f"来自实例 {instance}"
class MyClass:
attr = Descriptor()
# 通过类访问
print(MyClass.attr) # 返回描述器对象本身
# 通过实例访问
obj = MyClass()
print(obj.attr) # 返回 "来自实例..."

二、数据描述器 vs 非数据描述器#

2.1 定义与区别#

类型实现方法优先级行为
数据描述器__set____delete__高于实例字典可以拦截属性设置
非数据描述器__get__低于实例字典可以被实例属性覆盖

2.2 属性查找顺序#

Python 在访问 obj.attr 时的查找顺序:

# 1. 数据描述器(如果存在)
# 2. 实例 __dict__
# 3. 非数据描述器
# 4. 类 __dict__
# 5. 父类链(MRO)
# 6. __getattr__(如果定义)

2.3 示例:数据描述器 vs 非数据描述器#

# 数据描述器(实现 __set__)
class DataDescriptor:
def __get__(self, instance, owner):
return "数据描述器"
def __set__(self, instance, value):
pass # 只要实现就成为数据描述器
# 非数据描述器(仅实现 __get__)
class NonDataDescriptor:
def __get__(self, instance, owner):
return "非数据描述器"
class MyClass:
data = DataDescriptor()
non_data = NonDataDescriptor()
obj = MyClass()
# 数据描述器:无法被实例属性覆盖
obj.data = "尝试覆盖"
print(obj.data) # 输出: "数据描述器"(仍然是描述器)
# 非数据描述器:可以被实例属性覆盖
obj.non_data = "已覆盖"
print(obj.non_data) # 输出: "已覆盖"(实例属性)
# 删除实例属性后,描述器生效
del obj.non_data
print(obj.non_data) # 输出: "非数据描述器"

三、内置描述器:property、classmethod、staticmethod#

3.1 property 原理#

# property 是数据描述器的实现
class MyProperty:
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, instance, owner):
if instance is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(instance, value)
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(instance)
# 装饰器方法
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
# 使用自定义 property
class Person:
def __init__(self, name):
self._name = name
@MyProperty
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError("name must be a string")
self._name = value
p = Person("Alice")
print(p.name) # "Alice"
p.name = "Bob" # 调用 setter
print(p.name) # "Bob"

3.2 classmethod 原理#

# classmethod 是非数据描述器
class MyClassMethod:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
# 返回绑定到类的函数
return lambda *args, **kwargs: self.func(owner, *args, **kwargs)
class MyClass:
@MyClassMethod
def greet(cls):
return f"Hello from {cls.__name__}"
# 使用
print(MyClass.greet()) # "Hello from MyClass"
obj = MyClass()
print(obj.greet()) # "Hello from MyClass"(仍然绑定到类)

3.3 staticmethod 原理#

# staticmethod 是非数据描述器
class MyStaticMethod:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
# 直接返回原始函数,不绑定
return self.func
class MyClass:
@MyStaticMethod
def greet():
return "Hello!"
# 使用
print(MyClass.greet()) # "Hello!"
obj = MyClass()
print(obj.greet()) # "Hello!"(不绑定到任何对象)

四、描述器实战:ORM 字段实现#

4.1 基础字段类#

class Field:
"""ORM 字段基类"""
def __init__(self, name=None, primary_key=False, nullable=False):
self.name = name
self.primary_key = primary_key
self.nullable = nullable
def __set_name__(self, owner, name):
"""Python 3.6+ 新增:在类定义时调用,自动获取字段名"""
if self.name is None:
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
# 从实例字典获取值(如果存在)
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
# 类型验证(在子类中实现)
self.validate(value)
instance.__dict__[self.name] = value
def validate(self, value):
"""子类实现验证逻辑"""
pass
class IntegerField(Field):
"""整型字段"""
def __init__(self, min_value=None, max_value=None, **kwargs):
super().__init__(**kwargs)
self.min_value = min_value
self.max_value = max_value
def validate(self, value):
if value is None:
if not self.nullable:
raise ValueError(f"{self.name} cannot be null")
return
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}")
class StringField(Field):
"""字符串字段"""
def __init__(self, max_length=None, **kwargs):
super().__init__(**kwargs)
self.max_length = max_length
def validate(self, value):
if value is None:
if not self.nullable:
raise ValueError(f"{self.name} cannot be null")
return
if not isinstance(value, str):
raise TypeError(f"{self.name} must be a string")
if self.max_length is not None and len(value) > self.max_length:
raise ValueError(f"{self.name} length must be <= {self.max_length}")

4.2 模型基类与元类#

class ModelMeta(type):
"""模型元类:收集字段信息"""
def __new__(mcs, name, bases, namespace):
fields = {}
# 收集所有 Field 实例
for key, value in namespace.items():
if isinstance(value, Field):
fields[key] = value
# 存储字段信息
namespace['_fields'] = fields
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=ModelMeta):
"""模型基类"""
def __init__(self, **kwargs):
for field_name, field in self._fields.items():
if field_name in kwargs:
setattr(self, field_name, kwargs[field_name])
elif not field.nullable:
raise ValueError(f"{field_name} is required")
def save(self):
"""模拟保存到数据库"""
data = {}
for field_name, field in self._fields.items():
data[field_name] = getattr(self, field_name)
print(f"Saving {self.__class__.__name__}: {data}")
@classmethod
def get_fields(cls):
return cls._fields
# 使用示例
class User(Model):
id = IntegerField(primary_key=True)
name = StringField(max_length=100)
age = IntegerField(min_value=0, max_value=120)
email = StringField(max_length=255)
# 创建实例
user = User(id=1, name="Alice", age=30, email="alice@example.com")
user.save() # Saving User: {'id': 1, 'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
# 验证失败示例
try:
user2 = User(id=2, name="Bob", age="invalid", email="bob@example.com")
except TypeError as e:
print(e) # age must be an integer

五、描述器模式:验证器、缓存、懒加载#

5.1 验证器描述器#

class ValidatorDescriptor:
"""通用验证器描述器"""
def __init__(self, validator=None, default=None):
self.validator = validator
self.default = default
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name, self.default)
def __set__(self, instance, value):
if self.validator is not None:
if not self.validator(value):
raise ValueError(f"Invalid value for {self.name}")
instance.__dict__[self.name] = value
# 使用验证器
def is_positive(x):
return x > 0
def is_email(x):
return isinstance(x, str) and '@' in x
class Product:
price = ValidatorDescriptor(validator=is_positive)
email = ValidatorDescriptor(validator=is_email)
p = Product()
p.price = 100 # 正常
p.email = "test@example.com" # 正常
try:
p.price = -10 # 验证失败
except ValueError as e:
print(e) # Invalid value for price

5.2 缓存描述器#

class CachedDescriptor:
"""缓存描述器:延迟计算并缓存结果"""
def __init__(self, func):
self.func = func
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
# 检查缓存
if self.name not in instance.__dict__:
# 计算并缓存
result = self.func(instance)
instance.__dict__[self.name] = result
return instance.__dict__[self.name]
def __set__(self, instance, value):
# 允许手动设置缓存值
instance.__dict__[self.name] = value
class DataProcessor:
def __init__(self, data):
self.data = data
@CachedDescriptor
def processed_data(self):
"""模拟耗时计算"""
print("Processing data...")
# 模拟耗时操作
import time
time.sleep(1)
return [x * 2 for x in self.data]
# 使用
processor = DataProcessor([1, 2, 3, 4, 5])
# 第一次访问:计算并缓存
print(processor.processed_data) # 输出 "Processing data...",然后 [2, 4, 6, 8, 10]
# 第二次访问:直接返回缓存
print(processor.processed_data) # 直接返回 [2, 4, 6, 8, 10]

5.3 懒加载描述器#

class LazyLoadDescriptor:
"""懒加载描述器:按需加载资源"""
def __init__(self, loader):
"""
loader: 一个函数,接受实例作为参数,返回加载的值
"""
self.loader = loader
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
# 检查是否已加载
if self.name not in instance.__dict__:
# 懒加载
instance.__dict__[self.name] = self.loader(instance)
return instance.__dict__[self.name]
def __set__(self, instance, value):
instance.__dict__[self.name] = value
# 使用示例
def load_large_file(instance):
"""模拟加载大文件"""
print(f"Loading large file: {instance.filename}")
return f"Content of {instance.filename}"
class Document:
def __init__(self, filename):
self.filename = filename
content = LazyLoadDescriptor(loader=load_large_file)
# 创建文档(不立即加载)
doc = Document("large_data.txt")
# 访问 content 时才加载
print(doc.content) # 输出 "Loading large file: large_data.txt",然后 "Content of large_data.txt"
# 再次访问(已缓存)
print(doc.content) # 直接返回缓存内容

六、描述器组合与高级用法#

6.1 描述器链#

class LoggingDescriptor:
"""日志描述器:记录属性访问"""
def __init__(self, wrapped):
self.wrapped = wrapped
def __get__(self, instance, owner):
print(f"Accessing {self.wrapped.name}")
return self.wrapped.__get__(instance, owner)
def __set__(self, instance, value):
print(f"Setting {self.wrapped.name} = {value}")
self.wrapped.__set__(instance, value)
class ValidatedField:
"""验证字段"""
def __init__(self, name=None):
self.name = name
def __set_name__(self, owner, name):
if self.name is None:
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError(f"{self.name} must be int")
instance.__dict__[self.name] = value
# 创建带日志的验证字段
class MyClass:
# 组合描述器
count = LoggingDescriptor(ValidatedField())
obj = MyClass()
obj.count = 42 # 输出 "Setting count = 42"
print(obj.count) # 输出 "Accessing count",然后 42

6.2 描述器工厂#

def field_factory(validator=None, default=None, cached=False):
"""描述器工厂:根据参数创建不同类型的描述器"""
if cached:
# 创建带缓存的描述器
class CachedField:
def __init__(self):
self.validator = validator
self.default = default
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
if self.name not in instance.__dict__:
instance.__dict__[self.name] = self.default
return instance.__dict__[self.name]
def __set__(self, instance, value):
if self.validator and not self.validator(value):
raise ValueError(f"Invalid value")
instance.__dict__[self.name] = value
return CachedField()
else:
# 创建普通描述器
class SimpleField:
def __init__(self):
self.validator = validator
self.default = default
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name, self.default)
def __set__(self, instance, value):
if self.validator and not self.validator(value):
raise ValueError(f"Invalid value")
instance.__dict__[self.name] = value
return SimpleField()
# 使用工厂
class Config:
timeout = field_factory(validator=lambda x: x > 0, default=30)
cache_size = field_factory(validator=lambda x: x >= 0, cached=True)
cfg = Config()
print(cfg.timeout) # 30(默认值)
cfg.timeout = 60 # 设置值

七、常见陷阱与最佳实践#

❌ 陷阱 1:在 __get__ 中修改实例状态#

# ❌ 错误:每次访问都修改状态
class BadDescriptor:
def __get__(self, instance, owner):
instance.__dict__['counter'] = instance.__dict__.get('counter', 0) + 1
return instance.__dict__['counter']
# ✅ 正确:只读取,不修改
class GoodDescriptor:
def __get__(self, instance, owner):
return instance.__dict__.get('counter', 0)

❌ 陷阱 2:忘记处理 instance is None#

# ❌ 错误:通过类访问时崩溃
class BadDescriptor:
def __get__(self, instance, owner):
return instance.__dict__['value'] # instance 可能是 None
# ✅ 正确:检查 instance
class GoodDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self # 通过类访问返回描述器本身
return instance.__dict__.get('value')

❌ 陷阱 3:在 __set_name__ 中访问类属性#

# ❌ 错误:__set_name__ 被调用时类还未完全创建
class BadDescriptor:
def __set_name__(self, owner, name):
# owner 是正在创建的类,但可能不完整
owner.some_class_attr = True # 可能导致问题
# ✅ 正确:只存储字段名
class GoodDescriptor:
def __set_name__(self, owner, name):
self.name = name # 只存储字段名,不修改类

✅ 最佳实践清单#

原则说明
使用 __set_name__Python 3.6+ 推荐使用,自动获取字段名
处理 instance is None支持通过类访问描述器
使用实例字典存储值避免在描述器中存储实例特定数据
区分数据/非数据描述器根据需求选择类型
优先使用组合通过包装现有描述器扩展功能
简单场景用 property避免过度设计
复杂场景用类描述器代码更清晰、可复用

八、总结:描述器应用场景#

场景推荐方案原因
简单属性访问控制@property简洁易用
可复用验证逻辑自定义描述器类代码复用
ORM 字段映射数据描述器 + 元类强大灵活
缓存/懒加载非数据描述器可被实例覆盖
类型转换数据描述器拦截所有设置
日志/监控描述器包装器组合扩展

描述器是 Python 最强大的元编程工具之一。理解描述器协议不仅能让你使用 propertyclassmethod 等内置特性,还能创建自己的高级数据结构和框架。从简单的验证器到复杂的 ORM,描述器无处不在。掌握描述器,你就掌握了 Python 类属性访问的精髓。

Python 描述器(Descriptor)深度解析:从原理到 ORM 实战 | Python 进阶核心知识
https://971918.xyz/posts/python-guide/python-descriptor-details/
作者
九所长
发布于
2026-06-22
许可协议
CC BY-NC-SA 4.0