Python 描述符笔记
穿插些自己的理解
目前 Python 描述符协议有 4 个方法:
__get__
__set__
__delete__
-
__set_name__
(Python 3.6 新增)
描述符又分为两类:
- 数据描述符
- 非数据描述符
非数据描述符:仅实现了 __get__
方法
数据描述符:实现了 __set__
或 __delete__
方法
注意:__set_name__
:对改分类没有影响
(非数据描述符一般不会修改对象,而数据描述符会)
示例
# 示例来自 Clean Code in Python
# 具有当前城市的旅行者在程序运行期间跟踪用户访问过的所有城市
class HistoryTracedAttribute: # 描述符类
def __init__(self, trace_attribute_name) -> None:
self.trace_attribute_name = trace_attribute_name
self._name = None
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self._name]
def __set__(self, instance, value):
self._track_change_in_value_for_instance(instance, value)
instance.__dict__[self._name] = value
def _track_change_in_value_for_instance(self, instance, value):
self._set_default(instance)
if self._needs_to_track_change(instance, value):
instance.__dict__[self.trace_attribute_name].append(value)
def _needs_to_track_change(self, instance, value) -> bool:
try:
current_value = instance.__dict__[self._name]
except KeyError:
return True
return value != current_value
def _set_default(self, instance):
instance.__dict__.setdefault(self.trace_attribute_name, [])
class Traveller: # 所有者类
current_city = HistoryTracedAttribute("cities_visited")
def __init__(self, name, current_city):
self.name = name
self.current_city = current_city
描述符类一定要是所有者类的类属性
新增方法
__set_name__
签名:object.__set_name__(self, owner, name)
self
:描述符类实例
owner
:所有者类
name
:该变量会获取到所有者类属性的属性名
class A:
def __get__(self, instance, owner):
return 55
def __set_name__(self, owner, name):
print(name)
self._name = name
class B:
this_is_name = A()
>>> this_is_name
非数据描述符
__get__
签名:object.__get__(self, instance, owner=None)
self
:描述符类实例
instance
:所有者类实例
owner
:所有者类
该方法应返回计算出的属性值或引发AttributeError异常。
该方法在所有者实例的 __dict__
之后调用,即:
class A:
def __get__(self, instance, owner):
return 55
class B:
value = A()
b = B()
b.__dict__
>>> {}
b.value
>>> 55
b.value = 99
b.__dict__
>>> {'value': 99}
b.value
>>> 99
del b.value
b.value
>>> 55
b.__dict__
>>> {}
数据描述符
__set__
签名:object.__set__(self, instance, value)
self
:同上
instance
:同上
value
:赋值时传入的值
数据描述符使得所有描述符方法在所有者实例的 __dict__
之前调用,即:
class A:
def __get__(self, instance, owner):
if instance is None:
return self
return 55
def __set__(self, instance, value):
instance.__dict__[self._name] = value
def __set_name__(self, owner, name):
self._name = name
class B:
value = A()
b = B()
b.value = 99
b.__dict__
>>> {'value': 99}
b.value
>>> 55
__delete__
签名:object.__delete__(self, instance)
self
:同上
instance
:同上
在描述符实现了 __set__
方法的情况下使用,对所有者实例调用 del
语句会调用描述符的 __delete__
方法,如果没有就会报错:AttributeError: __delete__
class A:
def __get__(self, instance, owner):
if instance is None:
return self
return 55
def __set__(self, instance, value):
instance.__dict__[self._name] = value
def __set_name__(self, owner, name):
self._name = name
class B:
value = A()
b = B()
b.value = 99
b.__dict__
>>> {'value': 99}
b.value
>>> 55
del b.value
>>> AttributeError: __delete__
在描述符方法中不能使用 getattr
,setattr
。只能直接访问 __dict__
,否则会造成死递归,因为这两个方法内部调用的就是 __get__
,__set__
。
多个所有者实例的同一属性引用的是同一描述符对象:
class A:
def __get__(self, instance, owner):
if instance is None:
return self
return 55
def __set__(self, instance, value):
instance.__dict__[self._name] = value
def __set_name__(self, owner, name):
self._name = name
class B:
value = A()
b1 = B()
b2 = B()
id(b1) == id(b2)
>>> False
id(b1.value) == id(b2.value)
>>> True