装饰器+描述器 以及描述器的妙用

当装饰器遇到描述器,会发生什么化学反应呢?

装饰器+描述器的黑魔法

装饰器的用法,再回顾一下

1
2
3
@decorator
def function:
pass

这个函数等价于function = decorator(function)。那么可以利用装饰器来把一个函数当做变量(这里为了区分函数和变量,就称为变量,正确来说都叫属性),来访问了。

1. 先看装饰器@property

property的原型的Python等价表达如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
self.fdel(obj)

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)

对,proprety就是一个类?(正确来说叫描述器 >_<),那么最普通的用法呢就是使用property把一个类变量变成描述器,在对象属性访问的时候可以动态添加某些操作:

1
2
3
4
5
6
7
class Cell(object):
. . .
def getvalue(self, obj):
"Recalculate cell before returning value"
self.recalc()
return obj._value
value = property(getvalue)

2. 二者结合的结晶——黑魔法(@property)!_!

装饰器和描述器结合的产物子啊上文说过了就是@property。
还是来分析一下这个过程是怎样的吧,我们来看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
@property
def birth(self):
return self._birth

@birth.setter
def birth(self, value):
self._birth = value

@property
def age(self):
return 2015 - self._birth

Student类中第一个birth被@property修饰了,执行了 birth = property(birth),此时birth就已经不是一个函数了,而是一个描述器(而不是函数这类的描述器)。 那么下面那个birth函数被@birth.setter修饰,其实就是使描述器中__set__()函数合法。那么此时birth就是资料描述器了(Yeah)

什么?前面说的描述器是类属性只能是共享的?辣也不行!

其实这里property已经做到了通过描述器访问实例各自的属性。等价于(来自StackOverflow)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Desc(object):
default_value = 10
def __init__(self, name):
self.name = name
def __get__(self,obj,objtype):
return obj.__dict__.get(self.name, self.default_value)
# alternatively the following; but won't work with shadowing:
#return getattr(obj, self.name, self.default_value)
def __set__(self,obj,val):
obj.__dict__[self.name] = val
# alternatively the following; but won't work with shadowing:
#setattr(obj, self.name, val)
def __delete__(self,obj):
pass

描述器的妙用

Python描述器这份笔记中说明了描述器,以及描述器在Python中构建面向对象特性的作用。这里以Flask的缓存器为例子说一下描述器的妙用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class _Missing(object):
def __repr__(self):
return 'no value'
def __reduce__(self):
return '_missing'

_missing = _Missing()

class cached_property(object):
def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value

class Foo(object):
@cached_property
def foo(self):
print('first calculate')
result = 'this is result'
return result

f = Foo()
print f.foo # first calculate this is result
print f.foo # this is result

第一次求值并不返回,而是在第二次求值时才返回,实现了求值缓存的作用。

至此,描述器的内容就结束了,后面可能还会继续开坑写

Author: lisupy
Link: http://lisupy.github.io/2017/03/12/装饰器+描述器以及描述器的妙用/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
支付宝打赏
微信打赏