Python 中的描述符是什么?如何使用?
Python 描述符详解描述符的基本概念描述符是 Python 中实现属性访问控制的强大机制。描述符协议包含三个方法:__get__、__set__ 和 __delete__。任何实现了这些方法的对象都可以作为描述符使用。描述符协议class Descriptor: def __get__(self, obj, objtype=None): """获取属性值""" pass def __set__(self, obj, value): """设置属性值""" pass def __delete__(self, obj): """删除属性""" pass数据描述符 vs 非数据描述符数据描述符实现了 __get__ 和 __set__ 方法的描述符称为数据描述符。class DataDescriptor: def __init__(self, initial_value=None): self.value = initial_value def __get__(self, obj, objtype=None): print(f"获取数据描述符值: {self.value}") return self.value def __set__(self, obj, value): print(f"设置数据描述符值: {value}") self.value = valueclass MyClass: attr = DataDescriptor(42)obj = MyClass()print(obj.attr) # 获取数据描述符值: 42obj.attr = 100 # 设置数据描述符值: 100print(obj.attr) # 获取数据描述符值: 100非数据描述符只实现了 __get__ 方法的描述符称为非数据描述符。class NonDataDescriptor: def __init__(self, initial_value=None): self.value = initial_value def __get__(self, obj, objtype=None): print(f"获取非数据描述符值: {self.value}") return self.valueclass MyClass: attr = NonDataDescriptor(42)obj = MyClass()print(obj.attr) # 获取非数据描述符值: 42obj.attr = 100 # 设置实例属性,不调用 __set__print(obj.attr) # 100(实例属性优先)数据描述符 vs 非数据描述符的优先级class DataDesc: def __get__(self, obj, objtype=None): return "数据描述符" def __set__(self, obj, value): passclass NonDataDesc: def __get__(self, obj, objtype=None): return "非数据描述符"class MyClass: data_desc = DataDesc() non_data_desc = NonDataDesc()obj = MyClass()obj.data_desc = "实例值"obj.non_data_desc = "实例值"print(obj.data_desc) # 数据描述符(数据描述符优先)print(obj.non_data_desc) # 实例值(实例属性优先)描述符的实际应用1. 类型检查class Typed: """类型检查描述符""" def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): if not isinstance(value, self.expected_type): raise TypeError( f"属性 {self.name} 应该是 {self.expected_type} 类型," f"但得到的是 {type(value)}" ) obj.__dict__[self.name] = valueclass Person: name = Typed('name', str) age = Typed('age', int)person = Person()person.name = "Alice" # 正常person.age = 25 # 正常# person.age = "25" # TypeError: 属性 age 应该是 <class 'int'> 类型,但得到的是 <class 'str'>2. 值验证class Validated: """值验证描述符""" def __init__(self, name, validator): self.name = name self.validator = validator def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): if not self.validator(value): raise ValueError(f"属性 {self.name} 的值 {value} 无效") obj.__dict__[self.name] = valueclass Person: age = Validated('age', lambda x: isinstance(x, int) and 0 <= x <= 150) name = Validated('name', lambda x: isinstance(x, str) and len(x) > 0)person = Person()person.age = 25 # 正常person.name = "Alice" # 正常# person.age = -5 # ValueError: 属性 age 的值 -5 无效# person.name = "" # ValueError: 属性 name 的值 无效3. 延迟计算class LazyProperty: """延迟计算属性描述符""" def __init__(self, func): self.func = func self.name = func.__name__ def __get__(self, obj, objtype=None): if obj is None: return self # 检查是否已经计算过 if self.name not in obj.__dict__: print(f"延迟计算 {self.name}") obj.__dict__[self.name] = self.func(obj) return obj.__dict__[self.name]class Circle: def __init__(self, radius): self.radius = radius @LazyProperty def area(self): print("计算面积...") return 3.14159 * self.radius ** 2 @LazyProperty def circumference(self): print("计算周长...") return 2 * 3.14159 * self.radiuscircle = Circle(5)print(circle.area) # 延迟计算 area, 计算面积..., 78.53975print(circle.area) # 78.53975(直接返回缓存值)print(circle.circumference) # 延迟计算 circumference, 计算周长..., 31.41594. 只读属性class ReadOnly: """只读属性描述符""" def __init__(self, name, value): self.name = name self.value = value def __get__(self, obj, objtype=None): if obj is None: return self return self.value def __set__(self, obj, value): raise AttributeError(f"属性 {self.name} 是只读的")class Config: VERSION = ReadOnly('VERSION', '1.0.0') AUTHOR = ReadOnly('AUTHOR', 'Alice')config = Config()print(config.VERSION) # 1.0.0# config.VERSION = '2.0.0' # AttributeError: 属性 VERSION 是只读的5. 属性访问日志class Logged: """属性访问日志描述符""" def __init__(self, name): self.name = name def __get__(self, obj, objtype=None): if obj is None: return self value = obj.__dict__.get(self.name) print(f"读取属性 {self.name}: {value}") return value def __set__(self, obj, value): print(f"设置属性 {self.name}: {value}") obj.__dict__[self.name] = value def __delete__(self, obj): print(f"删除属性 {self.name}") del obj.__dict__[self.name]class Person: name = Logged('name') age = Logged('age')person = Person()person.name = "Alice" # 设置属性 name: Aliceperson.age = 25 # 设置属性 age: 25print(person.name) # 读取属性 name: Alicedel person.age # 删除属性 age6. 缓存属性class Cached: """缓存属性描述符""" def __init__(self, func): self.func = func self.name = func.__name__ self.cache = {} def __get__(self, obj, objtype=None): if obj is None: return self # 使用对象 ID 作为缓存键 obj_id = id(obj) if obj_id not in self.cache: print(f"计算并缓存 {self.name}") self.cache[obj_id] = self.func(obj) else: print(f"使用缓存 {self.name}") return self.cache[obj_id]class ExpensiveCalculator: def __init__(self, base): self.base = base @Cached def expensive_operation(self): print("执行耗时操作...") import time time.sleep(1) return self.base ** 2calc = ExpensiveCalculator(5)print(calc.expensive_operation) # 计算并缓存 expensive_operation, 执行耗时操作..., 25print(calc.expensive_operation) # 使用缓存 expensive_operation, 25描述符与 property 的关系property 本质上是描述符# property 实际上是一个描述符类class MyClass: @property def my_property(self): return "property 值" @my_property.setter def my_property(self, value): print(f"设置 property: {value}")obj = MyClass()print(obj.my_property) # property 值obj.my_property = "新值" # 设置 property: 新值自定义 property 类class MyProperty: """自定义 property 类""" 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, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("不可读") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("不可写") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("不可删除") self.fdel(obj) def getter(self, fget): self.fget = fget return self def setter(self, fset): self.fset = fset return self def deleter(self, fdel): self.fdel = fdel return selfclass Person: def __init__(self): self._name = "" @MyProperty def name(self): return self._name @name.setter def name(self, value): self._name = valueperson = Person()person.name = "Alice"print(person.name) # Alice描述符的高级用法描述符与类方法class ClassMethodDescriptor: """类方法描述符""" def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): if objtype is None: objtype = type(obj) return self.func.__get__(objtype, objtype)class MyClass: @ClassMethodDescriptor def class_method(cls): return f"类方法: {cls.__name__}"print(MyClass.class_method()) # 类方法: MyClass描述符与静态方法class StaticMethodDescriptor: """静态方法描述符""" def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): return self.funcclass MyClass: @StaticMethodDescriptor def static_method(): return "静态方法"print(MyClass.static_method()) # 静态方法描述符链class ValidatedTyped: """验证和类型检查组合描述符""" def __init__(self, name, expected_type, validator=None): self.name = name self.expected_type = expected_type self.validator = validator def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): # 类型检查 if not isinstance(value, self.expected_type): raise TypeError( f"属性 {self.name} 应该是 {self.expected_type} 类型" ) # 值验证 if self.validator and not self.validator(value): raise ValueError(f"属性 {self.name} 的值 {value} 无效") obj.__dict__[self.name] = valueclass Person: age = ValidatedTyped( 'age', int, lambda x: 0 <= x <= 150 ) name = ValidatedTyped( 'name', str, lambda x: len(x) > 0 )person = Person()person.name = "Alice"person.age = 25# person.age = "25" # TypeError# person.age = -5 # ValueError描述符的最佳实践1. 使用 set_name 方法(Python 3.6+)class Descriptor: """使用 __set_name__ 的描述符""" def __set_name__(self, owner, name): self.name = name self.private_name = f'_{name}' def __get__(self, obj, objtype=None): if obj is None: return self return getattr(obj, self.private_name) def __set__(self, obj, value): setattr(obj, self.private_name, value)class MyClass: attr = Descriptor()obj = MyClass()obj.attr = 42print(obj.attr) # 422. 避免描述符中的循环引用import weakrefclass WeakRefDescriptor: """使用弱引用的描述符""" def __init__(self): self.instances = weakref.WeakKeyDictionary() def __get__(self, obj, objtype=None): if obj is None: return self return self.instances.get(obj) def __set__(self, obj, value): self.instances[obj] = valueclass MyClass: attr = WeakRefDescriptor()obj = MyClass()obj.attr = 42print(obj.attr) # 423. 提供清晰的错误信息class ValidatedDescriptor: """提供清晰错误信息的描述符""" def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): if not isinstance(value, self.expected_type): raise TypeError( f"在类 {obj.__class__.__name__} 中," f"属性 '{self.name}' 应该是 {self.expected_type.__name__} 类型," f"但得到的是 {type(value).__name__}" ) obj.__dict__[self.name] = valueclass Person: age = ValidatedDescriptor('age', int)person = Person()# person.age = "25" # TypeError: 在类 Person 中,属性 'age' 应该是 int 类型,但得到的是 str描述符的实际应用案例1. ORM 模型字段class Field: """ORM 字段描述符""" def __init__(self, field_type, primary_key=False): self.field_type = field_type self.primary_key = primary_key self.name = None def __set_name__(self, owner, name): self.name = name def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(f'_{self.name}') def __set__(self, obj, value): if not isinstance(value, self.field_type): raise TypeError(f"字段 {self.name} 类型错误") obj.__dict__[f'_{self.name}'] = valueclass ModelMeta(type): """模型元类""" def __new__(cls, name, bases, attrs): fields = {} for key, value in list(attrs.items()): if isinstance(value, Field): fields[key] = value attrs['_fields'] = fields return super().__new__(cls, name, bases, attrs)class User(metaclass=ModelMeta): id = Field(int, primary_key=True) name = Field(str) age = Field(int)user = User()user.name = "Alice"user.age = 25print(user.name) # Alice2. 单位转换class Temperature: """温度单位转换描述符""" def __init__(self, name): self.name = name def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(f'_{self.name}') def __set__(self, obj, value): if isinstance(value, (int, float)): obj.__dict__[f'_{self.name}'] = value elif isinstance(value, str): if value.endswith('°C'): obj.__dict__[f'_{self.name}'] = float(value[:-2]) elif value.endswith('°F'): obj.__dict__[f'_{self.name}'] = (float(value[:-2]) - 32) * 5/9 else: raise ValueError("无效的温度格式") else: raise TypeError("无效的温度类型")class Weather: celsius = Temperature('celsius')weather = Weather()weather.celsius = "25°C"print(weather.celsius) # 25.0weather.celsius = "77°F"print(weather.celsius) # 25.0总结Python 描述符的核心概念:描述符协议:__get__、__set__、__delete__ 三个方法数据描述符:实现了 __get__ 和 __set__,优先级高于实例属性非数据描述符:只实现了 __get__,优先级低于实例属性实际应用:类型检查值验证延迟计算只读属性属性访问日志缓存属性描述符的优势:强大的属性访问控制可重用的属性逻辑清晰的代码组织与 Python 内置机制集成描述符的最佳实践:使用 __set_name__ 方法(Python 3.6+)避免循环引用,使用弱引用提供清晰的错误信息理解描述符的优先级规则考虑使用更简单的替代方案(property)描述符是 Python 中实现高级属性控制的核心机制,理解描述符对于深入掌握 Python 的面向对象编程非常重要。property、classmethod、staticmethod 等都是基于描述符实现的。