通常,描述器是具有“绑定行为”的对象属性,其属性访问已被描述器协议中的方法覆盖。这些方法是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