python描述器

通常,描述器是具有“绑定行为”的对象属性,其属性访问已被描述器协议中的方法覆盖。这些方法是get __(), set _()和\_delete __()。如果为对象定义了任何这些方法,则称其为描述器.

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

如果你想创建一个全新的实例属性,可以通过一个描述器类的形式来定义它的功 能。下面是一个例子:

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')
    y = Integer('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

当你这样做后,所有对描述器属性(比如x 或y) 的访问会被get () 、set () 和delete () 方法捕获到. 描述器只能在类 级别被定义,而不能为每个实例单独定义。


# 定义一个带__get__,__set__方法的描述符
In [5]: class D:
            def __init__(self):
                pass
            def __get__(self, instance, cls):
                print("D get...")
            def __set__(self, instance, val):
                self.value = val


In [6]: class TD:
            x = D()
            def __init__(self,x):
                self.x = x


In [11]: t = TD(1)
In [12]: t.x
D get...                #这里调用了描述符的__get__方法


# 去掉描述中的__set__方法
In [5]: class D:
            def __init__(self):
                pass
            def __get__(self, instance, cls):
                print("D get...")


In [6]: class TD:
            x = D()
            def __init__(self,x):
                self.x = x


In [11]: t = TD(1)
In [12]: t.x             #去掉__set__方法后并没调用描述符中的__get__方法
1

一个类,如果只定义了 get() 方法,而没有定义 set(), delete() 方法,则认为是非数据描述符; 反之,则成为数据描述符。 而非数据描述符的优先级是低于实例属性的。 python中的属性访问优先级: 1.getattribute()方法无条件调用 2.数据描述符(由1触发调用,若改写了getattribute(),可能会导致无法调用描述符) 3.实例对象的字典(若与描述符对象同名,会被覆盖) 4.类的字典 5.非数据描述符 6.父类的字典 7.getattr()方法


使用一个描述器类定义一个延迟计算属性

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)
            setattr(instance, self.func.__name__, value)
            return value

import math
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.area只计算一次,当第一次计算后,c.area的值会存在实例的字典中,而只定义了一个get方法的非数据描述器的优先级是小于实例属性的,所以会直接访问实例属性。

vars(...)
    vars([object]) -> dictionary

    Without arguments, equivalent to locals().
    With an argument, equivalent to object.__dict__.

Ref: 1.官方文档 2.python cookbook 3.https://www.cnblogs.com/Jimmy1988/p/6808237.html