Python的描述符可以用来代替实现大量相似模式(可以复用@property)的类而写相同的属性访问代码的情景。

Python通过描述符协议的实现来实现descriptor。描述符协议要求类实现__get____set__方法。这两个方法会对属性访问进行转译。

描述符

例如:

假设Descriptor是描述符类,Cache类中的一属性attr是Descriptor的实例。那么转译过程如下:

1
2
3
4
5
cache = Cache()

cache.attr == Cache.__dict__['attr'].__get__(cache, Cache)

cache.attr = 1 == Cache.__dict__['attr'].__set__(cache, 1)

实际上,这个转译过程抽象地看如下:

1
2
3
4
5
6
7
instance.attr 
# 等价于
instance_type.__dict__['attr'].__get__(instance, instance_type)

instance.attr = value
# 等价于
instance_type.__dict__['attr'].__set__(instance, value)

也就是说,我们通过某个实例访问由描述符创建的属性时,Python解析器会最终转换到访问描述符类的__set____get__方法。这样的好处就是,使用这两个特殊方法就可以复用所有本该在@property装修下具有某种访问控制逻辑的属性。

下面这个是缓存类常见的模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import weakref

class Descriptor:

def __init__(self, name):
self._name = name
self._cache = weakref.WeakKeyDictionary()

def __get__(self, instance, instance_type):
if instance is None:
return self
return self._cache.get(instance, None)

def __set__(self, instance, value):
self._cache[instance] = value

一个使用描述符协议的例子

本节以处理期末考试信息为例。

假定考试有很多门课,没门课程的分数为0到100之间。那么属性的特点就可以复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import weakref

class Grade:

def __init__(self):
self._value = weakref.WeakKeyDictionary()

def __get__(self, instance, instance_type):
if instance is None:
return self
return self._value.get(instance, 0)

def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError("grade must be between 0 and 100")
self._value[instance] = value

class Exam:

english = Grade()
physics = Grade()
computer = Grade()

使用WeakKeyDictionary是不为每个instance增加引用计数,以便立即回收不在使用的对象。这里:

1
2
3
4
5
6
7
8
9
10
exam = Exam()

exam.english = 90

# 实际上Python会转译
Exam.__dict__['english'].__set__(exam, 90)

print(exam.english)
# Python会转译
Exam.__dict__['english'].__get__(exam, Exam)

转载请包括本文地址:https://allenwind.github.io/blog/5195
更多文章请参考:https://allenwind.github.io/blog/archives/