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
Exam.__dict__['english'].__set__(exam, 90)
print(exam.english)
Exam.__dict__['english'].__get__(exam, Exam)
|
转载请包括本文地址:https://allenwind.github.io/blog/5195
更多文章请参考:https://allenwind.github.io/blog/archives/