当定义迭代器的时候,描述是实现迭代协议的对象,即实现__iter__方法的对象。同理,所谓描述器,即实现了描述符协议,即__get__, __set__, 和 __delete__方法的对象。
单看定义,还是比较抽象的。talk is cheap。看代码吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class WebFramework( object ):
def __init__( self , name = 'Flask' ):
self .name = name
def __get__( self , instance, owner):
return self .name
def __set__( self , instance, value):
self .name = value
webframework = WebFramework()
In [ 1 ]: PythonSite.webframework
Out[ 1 ]: 'Flask'
In [ 2 ]: PythonSite.webframework = 'Tornado'
In [ 3 ]: PythonSite.webframework
Out[ 3 ]: 'Tornado'
|
定义了一个类WebFramework,它实现了描述符协议__get__和__set__,该对象(类也是对象,一切都是对象)即成为了一个描述器。同时实现__get__和__set__的称之为资料描述器(data descriptor)。仅仅实现__get__的则为非描述器。两者的差别是相对于实例的字典的优先级。
如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。
描述器的调用
对于这类魔法,其调用方法往往不是直接使用的。例如装饰器需要用 @ 符号调用。迭代器通常在迭代过程,或者使用 next 方法调用。描述器则比较简单,对象属性的时候会调用。
1
2
3
4
|
In [ 15 ]: webframework = WebFramework()
In [ 16 ]: webframework.__get__(webframework, WebFramework)
Out[ 16 ]: 'Flask'
|
描述器的应用
描述器的作用主要在方法和属性的定义上。既然我们可以重新描述类的属性,那么这个魔法就可以改变类的一些行为。最简单的应用则是可以配合装饰器,写一个类属性的缓存。Flask的作者写了一个werkzeug网络工具库,里面就使用描述器的特性,实现了一个缓存器。
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
34
35
36
37
38
39
40
|
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
|
运行结果可见,first calculate只在第一次调用时候被计算之后就把结果缓存起来了。这样的好处是在网络编程中,对HTTP协议的解析,通常会把HTTP的header解析成python的一个字典,而在视图函数的时候,可能不知一次的访问这个header,因此把这个header使用描述器缓存起来,可以减少多余的解析。
描述器在python的应用十分广泛,通常是配合装饰器一起使用。强大的魔法来自强大的责任。描述器还可以用来实现ORM中对sql语句的"预编译"。恰当的使用描述器,可以让自己的Python代码更优雅。