描述符协议深入
描述符协议深入
描述符是Python中实现属性访问的基础机制。理解描述符协议是掌握Python对象模型的关键,它驱动着property、方法、类方法、静态方法等核心特性。
描述符协议基础
描述符是实现了以下任一方法的对象:
__get__(self, obj, objtype=None)- 获取属性__set__(self, obj, value)- 设置属性__delete__(self, obj)- 删除属性
class MyDescriptor:
"""自定义描述符示例"""
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
# 类访问,返回描述符本身
return self
print(f"获取 {self.name}")
return getattr(obj, f'_{self.name}', None)
def __set__(self, obj, value):
print(f"设置 {self.name} = {value}")
setattr(obj, f'_{self.name}', value)
def __delete__(self, obj):
print(f"删除 {self.name}")
delattr(obj, f'_{self.name}')
class MyClass:
x = MyDescriptor('x')
y = MyDescriptor('y')
obj = MyClass()
obj.x = 10 # 设置 x = 10
print(obj.x) # 获取 x → 10
del obj.x # 删除 x
数据描述符与非数据描述符
描述符分为两类,区分标准是是否实现了__set__或__delete__:
- 数据描述符:实现了
__get__和__set__(或__delete__) - 非数据描述符:只实现了
__get__
class DataDescriptor:
"""数据描述符"""
def __get__(self, obj, objtype=None):
return "数据描述符"
def __set__(self, obj, value):
print(f"数据描述符拦截设置: {value}")
class NonDataDescriptor:
"""非数据描述符"""
def __get__(self, obj, objtype=None):
return "非数据描述符"
class TestClass:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()
obj = TestClass()
# 数据描述符优先于实例属性
obj.data_desc = "实例值" # 数据描述符拦截设置: 实例值
print(obj.data_desc) # 数据描述符
# 实例属性优先于非数据描述符
obj.non_data_desc = "实例值"
print(obj.non_data_desc) # 实例值
优先级规则
# 优先级从高到低:
# 1. 数据描述符
# 2. 实例字典
# 3. 非数据描述符
class HighPriority:
"""高优先级描述符"""
def __get__(self, obj, objtype=None):
return "高优先级"
def __set__(self, obj, value):
pass
class LowPriority:
"""低优先级描述符"""
def __get__(self, obj, objtype=None):
return "低优先级"
class PriorityDemo:
high = HighPriority()
low = LowPriority()
obj = PriorityDemo()
obj.high = "实例值"
obj.low = "实例值"
print(obj.high) # 高优先级(数据描述符优先)
print(obj.low) # 实例值(非数据描述符被覆盖)
实现property
property就是基于数据描述符实现的:
class Property:
"""模拟property实现"""
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc or (fget.__doc__ if fget else None)
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):
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__)
class Temperature:
def __init__(self):
self._celsius = 0
@Property
def celsius(self):
"""温度(摄氏度)"""
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
@Property
def fahrenheit(self):
"""温度(华氏度)"""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
t = Temperature()
t.celsius = 25
print(f"摄氏: {t.celsius}°C") # 摄氏: 25°C
print(f"华氏: {t.fahrenheit}°F") # 华氏: 77.0°F
t.fahrenheit = 212
print(f"摄氏: {t.celsius}°C") # 摄氏: 100.0°C
方法描述符
Python中的方法就是非数据描述符:
class MethodDescriptor:
"""模拟方法描述符"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
return self.func
# 绑定方法
def bound_method(*args, **kwargs):
return self.func(obj, *args, **kwargs)
return bound_method
class Animal:
def __init__(self, name):
self.name = name
@MethodDescriptor
def speak(self):
return f"{self.name}在叫"
cat = Animal("猫")
dog = Animal("狗")
print(cat.speak()) # 猫在叫
print(dog.speak()) # 狗在叫
# 访问未绑定的方法
print(Animal.speak) # <function Animal.speak>
类方法和静态方法
class ClassMethodDescriptor:
"""类方法描述符"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if objtype is None:
objtype = type(obj)
def class_method(*args, **kwargs):
return self.func(objtype, *args, **kwargs)
return class_method
class StaticMethodDescriptor:
"""静态方法描述符"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
return self.func
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@ClassMethodDescriptor
def from_string(cls, date_str):
"""从字符串创建日期"""
year, month, day = map(int, date_str.split('-'))
return cls(year, month, day)
@StaticMethodDescriptor
def is_valid_date(year, month, day):
"""验证日期是否有效"""
if not (1 <= month <= 12):
return False
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if month == 2 and year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
days_in_month[2] = 29
return 1 <= day <= days_in_month[month]
# 使用
date1 = Date.from_string("2024-01-15")
print(f"{date1.year}-{date1.month}-{date1.day}")
print(Date.is_valid_date(2024, 2, 29)) # True(闰年)
print(Date.is_valid_date(2023, 2, 29)) # False
描述符的应用场景
缓存描述符
import time
from functools import wraps
class cached_property:
"""缓存属性描述符"""
def __init__(self, func):
self.func = func
self.attrname = None
def __set_name__(self, owner, name):
self.attrname = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
# 检查缓存
try:
cache = obj.__dict__
except AttributeError:
cache = {}
if self.attrname in cache:
return cache[self.attrname]
# 计算并缓存
value = self.func(obj)
cache[self.attrname] = value
return value
class DataAnalysis:
def __init__(self, data):
self.data = data
@cached_property
def mean(self):
"""计算平均值(只计算一次)"""
print("计算平均值...")
time.sleep(1) # 模拟耗时计算
return sum(self.data) / len(self.data)
@cached_property
def std(self):
"""计算标准差(只计算一次)"""
print("计算标准差...")
time.sleep(1)
mean = self.mean
variance = sum((x - mean) ** 2 for x in self.data) / len(self.data)
return variance ** 0.5
analysis = DataAnalysis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(analysis.mean) # 计算平均值...
print(analysis.mean) # 直接返回缓存
print(analysis.std) # 计算标准差...
验证描述符链
class ValidatorChain:
"""验证器链描述符"""
def __init__(self, *validators):
self.validators = validators
self.attrname = None
def __set_name__(self, owner, name):
self.attrname = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, f'_{self.attrname}', None)
def __set__(self, obj, value):
for validator in self.validators:
value = validator(obj, value, self.attrname)
setattr(obj, f'_{self.attrname}', value)
def not_empty(obj, value, name):
if not value:
raise ValueError(f"{name} 不能为空")
return value
def min_length(min_len):
def validator(obj, value, name):
if len(value) < min_len:
raise ValueError(f"{name} 长度不能小于 {min_len}")
return value
return validator
class User:
username = ValidatorChain(not_empty, min_length(3))
email = ValidatorChain(not_empty)
def __init__(self, username, email):
self.username = username
self.email = email
try:
user = User("ab", "test@example.com") # ValueError
except ValueError as e:
print(e) # username 长度不能小于 3
总结
描述符是Python属性访问机制的核心:
- 数据描述符(实现
__set__):优先于实例属性 - 非数据描述符(只实现
__get__):实例属性可以覆盖 - property:最常用的数据描述符
- 方法:都是非数据描述符
理解描述符能让你更好地理解Python的内部机制,也能创建强大的自定义属性行为。