一、描述符协议:__get__、__set__、__delete__
描述符的本质
描述符是实现了特定协议的类,它控制对其他对象属性的访问。这是Python属性、方法、静态方法等特性的基础:
python
class ValidatedAttribute:
"""数据验证描述符"""
def __init__(self, validator):
self.validator = validator
self.attr_name = None # 稍后由__set_name__设置
def __set_name__(self, owner, name):
"""在描述符被赋值给类属性时调用(Python 3.6+)"""
self.attr_name = name
self.storage_name = f"_{name}" # 实际存储的私有属性名
def __get__(self, obj, objtype=None):
"""获取属性值时调用"""
if obj is None:
return self # 通过类访问时返回描述符本身
return getattr(obj, self.storage_name, None)
def __set__(self, obj, value):
"""设置属性值时调用"""
if not self.validator(value):
raise ValueError(f"Invalid value for {self.attr_name}: {value}")
setattr(obj, self.storage_name, value)
def __delete__(self, obj):
"""删除属性时调用"""
delattr(obj, self.storage_name)
def is_positive_number(value):
"""验证函数:必须是正数"""
return isinstance(value, (int, float)) and value > 0
class Product:
# 使用描述符
price = ValidatedAttribute(is_positive_number)
quantity = ValidatedAttribute(is_positive_number)
def __init__(self, name, price, quantity):
self.name = name
self.price = price # 触发__set__
self.quantity = quantity # 触发__set__
# 使用
try:
p = Product("Apple", 10.5, 5)
print(p.price) # 10.5,触发__get__
p.price = -5 # 触发__set__,抛出ValueError
except ValueError as e:
print(f"验证错误: {e}")
p.quantity = 20 # 正常设置
二、描述符的类型
1. 数据描述符(实现__set__或__delete__)
python
class LoggedAttribute:
"""记录所有访问的数据描述符"""
def __init__(self):
self.data = {}
def __get__(self, obj, objtype):
if obj is None:
return self
print(f"读取 {obj}.{self.attr_name}")
return self.data.get(id(obj))
def __set__(self, obj, value):
print(f"设置 {obj}.{self.attr_name} = {value}")
self.data[id(obj)] = value
def __set_name__(self, owner, name):
self.attr_name = name
2. 非数据描述符(只实现__get__)
python
class CachedProperty:
"""属性缓存描述符(非数据描述符)"""
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, obj, objtype):
if obj is None:
return self
# 计算并缓存结果
value = self.func(obj)
obj.__dict__[self.name] = value # 存储在实例字典中
return value
class Circle:
def __init__(self, radius):
self.radius = radius
@CachedProperty
def area(self):
print("计算面积...")
return 3.14159 * self.radius ** 2
@CachedProperty
def circumference(self):
print("计算周长...")
return 2 * 3.14159 * self.radius
c = Circle(5)
print(c.area) # 计算面积... 78.53975
print(c.area) # 78.53975(从缓存读取,不再打印"计算面积...")
三、Python内置描述符的实现
1. property装饰器的实现原理
python
class MyProperty:
"""简化版的property实现"""
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel)
class Person:
def __init__(self, name):
self._name = name
@MyProperty
def name(self):
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("Name cannot be empty")
self._name = value
p = Person("Alice")
print(p.name) # Alice
p.name = "Bob"
print(p.name) # Bob
2. 方法和静态方法的描述符实现
python
import types
class MyMethod:
"""模拟方法描述符"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype):
if obj is None:
return self.func # 通过类访问
# 绑定方法:将实例作为第一个参数
return types.MethodType(self.func, obj)
class MyStaticMethod:
"""模拟静态方法描述符"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
# 始终返回原始函数
return self.func
class MyClass:
@MyMethod
def instance_method(self):
return f"实例方法,self={self}"
@MyStaticMethod
def static_method():
return "静态方法"
obj = MyClass()
print(obj.instance_method()) # 实例方法,self=<__main__.MyClass object>
print(MyClass.static_method()) # 静态方法
四、描述符的应用场景
1. ORM(对象关系映射)框架
python
class Field:
"""数据库字段描述符"""
def __init__(self, field_type, nullable=True, default=None):
self.field_type = field_type
self.nullable = nullable
self.default = default
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype):
if obj is None:
return self
# 从实例的__dict__中获取值
return obj.__dict__.get(self.name, self.default)
def __set__(self, obj, value):
if value is None and not self.nullable:
raise ValueError(f"{self.name} cannot be null")
if value is not None and not isinstance(value, self.field_type):
raise TypeError(f"{self.name} must be {self.field_type}")
obj.__dict__[self.name] = value
class ModelMeta(type):
"""元类,用于收集描述符"""
def __new__(cls, name, bases, attrs):
# 收集所有Field描述符
fields = {}
for key, value in 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, nullable=False)
name = Field(str, nullable=False)
email = Field(str, default="")
def __init__(self, **kwargs):
for field_name, field in self._fields.items():
value = kwargs.get(field_name, field.default)
setattr(self, field_name, value)
def to_dict(self):
return {name: getattr(self, name) for name in self._fields}
# 使用
user = User(id=1, name="Alice")
print(user.to_dict()) # {'id': 1, 'name': 'Alice', 'email': ''}
2. 类型检查和自动转换
python
class TypedAttribute:
"""类型检查和自动转换描述符"""
def __init__(self, expected_type, converter=None):
self.expected_type = expected_type
self.converter = converter
self.storage_name = None
def __set_name__(self, owner, name):
self.storage_name = f"_{name}"
def __get__(self, obj, objtype):
if obj is None:
return self
return getattr(obj, self.storage_name, None)
def __set__(self, obj, value):
if value is None:
setattr(obj, self.storage_name, None)
return
# 尝试类型转换
if self.converter and not isinstance(value, self.expected_type):
try:
value = self.converter(value)
except (ValueError, TypeError):
pass
# 类型检查
if not isinstance(value, self.expected_type):
raise TypeError(
f"Expected {self.expected_type.__name__}, "
f"got {type(value).__name__}"
)
setattr(obj, self.storage_name, value)
class Config:
port = TypedAttribute(int, converter=int)
timeout = TypedAttribute(float, converter=float)
debug = TypedAttribute(bool, converter=bool)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
cfg = Config(port="8080", timeout="30.5", debug="true")
print(cfg.port) # 8080 (int)
print(cfg.timeout) # 30.5 (float)
print(cfg.debug) # True (bool)
五、描述符的查找顺序
Python的属性查找链
python
class Demo:
"""演示属性查找顺序"""
class_descriptor = "类属性(描述符)"
def __init__(self):
self.instance_attr = "实例属性"
def __getattr__(self, name):
"""找不到属性时调用"""
return f"__getattr__: {name}"
def __getattribute__(self, name):
"""总是首先调用"""
print(f"__getattribute__: {name}")
return super().__getattribute__(name)
class DataDescriptor:
"""数据描述符"""
def __get__(self, obj, objtype):
return "数据描述符"
def __set__(self, obj, value):
pass
class NonDataDescriptor:
"""非数据描述符"""
def __get__(self, obj, objtype):
return "非数据描述符"
class Test:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()
obj = Test()
obj.non_data_desc = "实例属性覆盖非数据描述符"
print(obj.data_desc) # "数据描述符"(优先于实例属性)
print(obj.non_data_desc) # "实例属性覆盖非数据描述符"
总结
描述符协议是Python最强大的特性之一,它实现了:
- 属性访问控制:验证、转换、记录属性访问
- 方法绑定机制:将函数转换为绑定方法
- 内置装饰器基础 :
@property、@classmethod、@staticmethod - 框架和库的核心:Django的Model字段、SQLAlchemy的Column等