class MyProperty:
def __init__(self, fget=None, fset=None, fdel=None):
# 接收getter、setter、deleter函数
self.fget = fget
self.fset = fset
self.fdel = fset
def __get__(self, instance, owner):
if self.fget:
return self.fget(instance)
def __set__(self, instance, value):
if self.fset:
self.fset(instance, value)
else:
raise AttributeError("该属性不可赋值")
# 实现装饰器的setter方法(模仿@property.setter)
def setter(self, func):
self.fset = func
return self
# 用MyProperty替代@property
class Person:
def __init__(self):
self._name = None # 私有变量
# 用MyProperty定义name属性
@MyProperty
def name(self):
return self._name
# 用MyProperty.setter定义赋值逻辑
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError("名字必须是字符串")
self._name = value
# 测试
p = Person()
p.name = "李四" # 触发MyProperty.__set__
print(p.name) # 触发MyProperty.__get__,输出:李四
try:
p.name = 123 # 非字符串,触发异常
except TypeError as e:
print(e) # 输出:名字必须是字符串
结论:@property 本质是对描述器的封装,让我们无需手动实现 __get__/__set__ 就能实现属性管控。
四、实际应用场景(企业开发中常用)
- ORM框架字段定义:如Django ORM的
models.IntegerField、models.CharField,底层用描述器实现字段类型校验、数据转换(数据库类型<->Python类型)。
- 配置类属性管控:如项目配置类中,用描述器限制配置项的类型、值范围(如端口必须是0-65535的整数)。
- 缓存/懒加载:如前面的案例,延迟加载耗时数据,提升程序启动速度。
- 权限控制:在属性访问时,通过描述器校验用户权限(如某些属性仅管理员可访问)。
五、常见坑点(避坑指南)
- 递归调用:在
__get__/__set__ 中直接访问 instance.attr 会再次触发描述器,导致递归栈溢出,需用 instance.__dict__ 直接操作。
- 类属性 vs 实例属性:描述器通常定义为类属性(如
class User: age = IntField()),若定义为实例属性则无法生效。
- 优先级混淆:数据描述器优先级最高,若想覆盖数据描述器的属性,需直接操作
instance.__dict__(不推荐)。
六、总结
描述器是Python OOP的底层核心机制,虽然日常开发中不常直接写,但很多高级特性(property、ORM字段)都依赖它。掌握描述器的价值在于:
- 理解Python属性访问的底层逻辑,遇到相关问题能快速定位。
- 实现灵活的属性管控,应对复杂业务场景(如类型校验、懒加载)。
- 读懂框架源码(如Django、Flask)中关于属性管控的实现。
建议:把本文的代码逐行运行一遍,修改参数、补充逻辑(如给LazyLoad添加缓存过期功能),加深理解。
参考资料:Python官方文档 - Descriptor HowTo Guide(https://docs.python.org/zh-cn/3/howto/descriptor.html)