描述器

1. Python中,一个类中实现了__get__、__set__、__delete__三个方法中的任何一个方法,那么这个类就是描述器。

     1)如果仅实现了__get__,就是非数据描述器(non-data descriptor)。

     2)同时实现了除__get__以外的__set__或__delete__方法,就是数据描述器(data descriptor)。

     二者的区别是:当属性名和描述器名相同时,在访问这个同名属性时,如果是数据描述器就会先访问描述器,如果是非数据描述器就会先访问属性。

2. 如果一个类的类属性为描述器实例,那么它被称为此描述器的拥有者。

3. 实例访问属性的顺序:以查找 a.x 为例,

    1)首先查找 a.__dict__['x']

    2)然后是 type(a).__dict__['x']

    3)接下来依次查找 type(a) 的基类,不包括元类

  在上面三个步骤中,只要在字典内找到属性 x,都会先查看 x 是否是一个描述器,如果是的话,则用描述器相关方法来取代默认行为。

    1)当属主类中对是描述器的类属性进行访问时(即类似b.x),__get__方法被触发。

    2)当属主类中对是描述器的实例属性通过'.'号赋值时,__set__方法被触发。

class Descriptor:
    """
    创建一个数据描述器
    """
    def __init__(self):
        self.x = 1

    """
    self    : 指当前类的实例本身
    instance: owner 的实例,即描述器所属类的实例对象
    owner   : 描述器所属的类,即type(instance)
    """
    def __get__(self, instance, owner):
        return self.x

    def __set__(self, instance, value):
        self.x = value

class OwnerClass:
    """
    描述器拥有者
    """
    m = Descriptor()  # m 就是一个描述器
    n = 2

    def __init__(self, score):
        self.score = score

owner = OwnerClass(3)
print(owner.m)  # owner.__dict__中找不到m,于是到type(owner).__dict__中找, 先去判断 m 是一个描述器,于是调用__get__方法,将self.x的值返回。
print(type(owner).__dict__['m'].__get__(owner, OwnerClass))  # 上面一条的调用方式是这样的

owner.m = 100   # 相当于调用 type(owner).__dict__['m'].__set__(owner, 100)

print(OwnerClass.m)  # 也是一样调用了描述器
print(OwnerClass.__dict__['m'].__get__(None, OwnerClass))  # 类相当于调用这个

4. 描述器的一个比较困惑的地方是它只能在类级别被定义(类属性),而不能为每个实例单独定义(即定义成实例属性会无法工作) 

   那想定义成实例属性该怎么做呢?可以使用一个描述器来间接操作实例属性,举个例子:

class Integer:
    def __init__(self, name):
        self.name = name  # 保存需要封装的属性名字

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

class Point:
    x = Integer('x')  # 封装实例属性 x
    y = Integer('y')  # 封装实例属性 y

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(2, 3)
p.y = 5
print(p.x)
print(p.y)

5. 使用描述器实现延迟计算

   很多时候,构造一个延迟计算属性的主要目的是为了提升性能你想将一个只读属性定义成一个property,并且只在访问的时候才会计算结果。

   但是一旦被访问后,你希望结果值被缓存起来,不用每次都去计算。定义一个延迟属性的一种高效方法是通过使用一个描述器类,如下:

import math

class Lazyproperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            """
            重点是下面这句代码,
            在__get__()方法中,调用实例的 area(self) 方法计算出结果,并动态给实例添加一个同名属性 area,
            然后将计算出的值赋予给它,相当于设置c.__dict__['area'] = val,相当于增加了一个属性
            """
            setattr(instance, self.func.__name__, value)
            return value

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @Lazyproperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

    @Lazyproperty
    def perimeter(self):
        print('Computing perimeter')
        return 2 * math.pi * self.radius

c = Circle(1)
print(c.__dict__)   # {'radius': 1}
print(c.area)
print(c.area)       # 只输出一次 Computing area
print(c.__dict__)   # {'radius': 1, 'area': 3.141592653589793}

  

posted @ 2020-06-29 08:42  _yanghh  阅读(307)  评论(0编辑  收藏  举报