2891 字
14 分钟
Python 描述器(Descriptor)深度解析:从原理到 ORM 实战 | Python 进阶核心知识
描述器(Descriptor)是 Python 元编程的核心概念之一,是理解 property、classmethod、staticmethod 等内置特性的关键。掌握描述器可以让你编写更优雅、更强大的代码。

本文将深入讲解描述器的完整知识体系:
- 描述器协议(
__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__ 被调用,输出: 421.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_dataprint(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__)
# 使用自定义 propertyclass 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" # 调用 setterprint(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 price5.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",然后 426.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
# ✅ 正确:检查 instanceclass 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 最强大的元编程工具之一。理解描述器协议不仅能让你使用 property、classmethod 等内置特性,还能创建自己的高级数据结构和框架。从简单的验证器到复杂的 ORM,描述器无处不在。掌握描述器,你就掌握了 Python 类属性访问的精髓。
Python 描述器(Descriptor)深度解析:从原理到 ORM 实战 | Python 进阶核心知识
https://971918.xyz/posts/python-guide/python-descriptor-details/