Python 描述符

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__

在描述符方法中不能使用 getattrsetattr 。只能直接访问 __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

你可能感兴趣的:(python描述符)