Python 描述符协议
描述符是一个类,通过定义 __get__、__set__、__delete__ 方法控制属性访问行为。
描述符类型
- 数据描述符:定义
__get__和__set__ - 非数据描述符:只定义
__get__
基本结构
Python
class MyDescriptor:
def __get__(self, instance, owner):
print("获取属性")
return self.value
def __set__(self, instance, value):
print(f"设置属性: {value}")
self.value = value
def __delete__(self, instance):
print("删除属性")
del self.value
class MyClass:
attr = MyDescriptor()
obj = MyClass()
obj.attr = 10 # 设置属性: 10
print(obj.attr) # 获取属性 → 10
del obj.attr # 删除属性
描述符方法签名
Python
class Descriptor:
def __get__(self, instance, owner):
"
instance: 访问属性的对象实例(类访问时为 None)
owner: 属性所在的类
"
pass
def __set__(self, instance, value):
"
instance: 设置属性的对象实例
value: 设置的值
"
pass
def __delete__(self, instance):
"
instance: 删除属性的对象实例
"
pass
类型检查描述符
Python
class Typed:
"类型检查描述符"
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"{self.name} 必须是 {self.expected_type}")
instance.__dict__[self.name] = value
class Person:
name = Typed('name', str)
age = Typed('age', int)
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 25)
print(p.name) # Alice
p.age = 30 # 正常
p.age = "thirty" # TypeError: age 必须是 int
非负值描述符
Python
class NonNegative:
"非负值描述符"
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value < 0:
raise ValueError(f"{self.name} 不能为负数")
instance.__dict__[self.name] = value
class Account:
balance = NonNegative('balance')
def __init__(self, balance):
self.balance = balance
acc = Account(100)
print(acc.balance) # 100
acc.balance = -50 # ValueError: balance 不能为负数
惰性计算描述符
Python
class LazyProperty:
"惰性计算描述符(非数据描述符)"
def __init__(self, func):
self.func = func
self.attr_name = func.__name__
def __get__(self, instance, owner):
if instance is None:
return self
# 计算并缓存到实例 __dict__
value = self.func(instance)
instance.__dict__[self.attr_name] = value
return value
class DataProcessor:
@LazyProperty
def expensive_data(self):
print("计算耗时数据...")
return [x ** 2 for x in range(10000)]
dp = DataProcessor()
print(dp.expensive_data) # 计算耗时数据...(第一次调用)
print(dp.expensive_data) # 直接返回缓存(不再计算)
实现 property
描述符是 @property 的底层实现:
Python
class Property:
"模拟 property"
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
if instance is None:
return self
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("不可写")
self.fset(instance, value)
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError("不可删除")
self.fdel(instance)
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel = fdel
return self
class Circle:
def __init__(self, radius):
self._radius = radius
@Property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负")
self._radius = value
c = Circle(5)
print(c.radius) # 5
c.radius = 10 # 正常
c.radius = -1 # ValueError
实现 staticmethod 和 classmethod
Python
class StaticMethod:
"模拟 staticmethod"
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return self.func
class ClassMethod:
"模拟 classmethod"
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return self.func.__get__(owner, owner)
class Demo:
@StaticMethod
def static_func():
return "static"
@ClassMethod
def class_func(cls):
return f"class: {cls.__name__}"
print(Demo.static_func()) # static
print(Demo.class_func()) # class: Demo
print(Demo().static_func()) # static
print(Demo().class_func()) # class: Demo
属性查找优先级
Python
访问 obj.attr 的顺序:
1. 数据描述符的 __get__(定义 __set__ 或 __delete__)
2. 实例 __dict__['attr']
3. 非数据描述符的 __get__(只定义 __get__)
4. 类 __dict__['attr'] 及继承链
5. __getattr__
优先级验证
Python
class DataDescriptor:
def __get__(self, instance, owner):
return "数据描述符"
def __set__(self, instance, value):
pass
class NonDataDescriptor:
def __get__(self, instance, owner):
return "非数据描述符"
class Test:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()
t = Test()
t.data_desc = "实例值"
print(t.data_desc) # 数据描述符(优先于实例 __dict__)
t.non_data_desc = "实例值"
print(t.non_data_desc) # 实例值(实例 __dict__ 优先)
描述符存储策略
Python
# 错误:存储在描述符自身(多实例共享)
class BadDescriptor:
def __init__(self):
self.value = None
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = value
# 正确:存储在实例 __dict__
class GoodDescriptor:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
class Container:
value = GoodDescriptor('value')
c1 = Container()
c2 = Container()
c1.value = 10
c2.value = 20
print(c1.value) # 10
print(c2.value) # 20(独立存储)
set_name 方法
Python 3.6+ 提供自动获取属性名:
text
class AutoNamedDescriptor:
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)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
class Point:
x = AutoNamedDescriptor() # 自动获取 name = 'x'
y = AutoNamedDescriptor() # 自动获取 name = 'y'
p = Point()
p.x = 10
p.y = 20
print(p.x) # 10
要点总结
| 要点 | 说明 |
|---|---|
| 数据描述符 | 定义 __get__ + __set__,优先于实例字典 |
| 非数据描述符 | 只定义 __get__,实例字典优先 |
| 存储位置 | 值应存储在实例 __dict__,避免共享 |
__set_name__ | Python 3.6+ 自动获取属性名 |
property | 本质是非数据描述符 |
描述符是 Python 属性系统的核心机制,property、classmethod、staticmethod 都是描述符实现。
D:\git2\jwdev\articles\PYTHON\进阶\面向对象编程\描述符协议.md
📝 发现内容有误?点击此处直接编辑