python中的描述符能够用来定义触发自动执行的代码,它像是一个对象属性操做(访问、赋值、删除)的代理类同样。前面介绍过的property是描述符的一种。python
大体流程是这样的:git
__get__()
、__set__()
、__delete__()
方法attr = D()
__get__()
、__set__()
、__delete__()
方法简言之,就是建立一个描述符类,它的实例对象做为另外一个类的属性。github
要定义描述符类很简单,只要某个类中包含了下面一个或多个方法,就算是知足描述符协议,就是描述符类,就能够做为属性操做的代理器。数据结构
class Descriptor(): def __get__(self, instance, owner):... def __set__(self, instance, value):... def __delete__(self, instance):...
须要注意的是,__get__
的返回值须要是属性值或抛异常,另外两个方法要返回None。函数
还需注意的是不要把__delete__
和__del__
搞混了,前者是实现描述符协议的一个方法,后者是对象销毁函数(也常称为析构函数)。测试
先无论这几个方法中的参数,看一个示例先:ui
class Descriptor(): def __get__(self, instance, owner): print("self: %s\ninstance: %s\nowner: %s" % (self, instance, owner)) class S: # 描述符的示例对象做为S的属性 attr = Descriptor() s1 = S() s1.attr # 访问对象属性 print("-" * 30) S.attr # 访问类属性
输出结果:url
self: <__main__.Descriptor object at 0x030C02D0> instance: <__main__.S object at 0x030C0AB0> owner: <class '__main__.S'> ------------------------------ self: <__main__.Descriptor object at 0x030C02D0> instance: None owner: <class '__main__.S'>
不难看出,在访问类S中的属性attr时,表示访问描述符类的实例对象,它会自动调用描述符类中的__get__
方法。代理
在这个方法中,3个参数self、instance、owner分别对应的内容从结果中已经显示出来了。它们之间有如下等价关系:code
s1.attr -> Descriptor.__get__(S.attr, s1, S) S.attr -> Descriptor.__get__(S.attr, None, S)
因此,这里解释下__get__(self, instance, owner)
中的三个参数:
再解释下这里相关的几个角色:
Descriptor
:是描述符类,也是代理者S
:是另外一个类,是托管类、客户类,也就是参数中的ownerattr = Descriptor()
:是描述符的实例对象,attr是托管类的属性,也就参数中的selfs1
:是托管类实例对象,也就是参数中的instance按照descriptor的功能,大概能够用上面的方式去定义各个角色。固然,角色的定义没什么限制。
当定义了一个类后,能够访问、赋值、删除它的属性,这些操做也一样适用于它的实例对象。
例如Foo类:
class Foo(): ... f = Foo() a = f.bar # 访问属性 f.bar = b # 赋值属性 del f.bar # 删除属性
decriptor发挥做用的时候就在于执行这3类操做的时候:
__get__
__set__
__delete__
考虑一下:若是x所属的类中已经定义了__getattr__
、__setattr__
、__delattr__
会如何,是描述符类中的先生效,仍是x自身所属类的这几个方法会生效。再继续考虑,若是x所属类没有定义,但它的父类定义了这几个方法,谁会生效。可自行测试或者参考个人下一篇文章。
假设如今有一个Student类,须要记录stuid、name、score一、score二、score3信息。
class Student(): def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name self.score1 = score1 self.score2 = score2 self.score3 = score3 def returnMe(self): return "%s, %s, %i, %i, %i" % ( self.stuid, self.name, self.score1, self.score2, self.score3) stu = Student("20101120", "malong", 67, 77, 88) print(stu.returnMe())
可是如今有个需求,要求score1-score3
的数值范围只能是0-100分。
因而修改__init__()
:
class Student(): def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name if 0 <= score1 <= 100: self.score1 = score1 else: raise ValueError("score not in [0,100]") if 0 <= score2 <= 100: self.score2 = score2 else: raise ValueError("score not in [0,100]") if 0 <= score3 <= 100: self.score3 = score3 else: raise ValueError("score not in [0,100]")
这个修改对于初始化Student对象时有效,但Python中属性的赋值太过自由,以后能够随意赋值:
stu = Student("20101120", "malong", 67, 77, 88) stu.score1 = -23 print(stu.returnMe())
使用Property或者自定义的getter、setter或运算符__getattr__
、__setattr__
重载都能解决上面的问题,保证没法赋值超出0到100范围内的数值。
class Student(): def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name self._score1 = score1 self._score2 = score2 self._score3 = score3 def get_score1(self): return self._score1 def set_score1(self, score): if 0 <= score <= 100: self._score1 = score else: raise ValueError("score not in [0,100]") def get_score2(self): return self._score2 def set_score2(self, score): if 0 <= score <= 100: self._score2 = score else: raise ValueError("score not in [0,100]") def get_score3(self): return self._score3 def set_score3(self, score): if 0 <= score <= 100: self._score3 = score else: raise ValueError("score not in [0,100]") score1 = property(get_score1, set_score1) score2 = property(get_score2, set_score2) score3 = property(get_score3, set_score3) def returnMe(self): return "%s, %s, %i, %i, %i" % ( self.stuid, self.name, self.score1, self.score2, self.score3)
下面测试时将抛出异常。
stu = Student("20101120", "malong", 67, 77, 88) print(stu.returnMe()) stu.score1 = -23
但很显然,上面的重复代码太多了。
若是使用descriptor,将很容易解决上面的问题。只需将score一、score二、score3交给描述符类托管便可。
from weakref import WeakKeyDictionary class Score(): """ score should in [0,100] """ def __init__(self): self.score = WeakKeyDictionary() #self.score = {} def __get__(self, instance, owner): return self.score[instance] def __set__(self, instance, value): if 0 <= value <= 100: self.score[instance] = value else: raise ValueError("score not in [0,100]") class Student(): # 托管属性定义在类级别上 score1 = Score() score2 = Score() score3 = Score() def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name self.score1 = score1 self.score2 = score2 self.score3 = score3 def returnMe(self): return "%s, %s, %i, %i, %i" % ( self.stuid, self.name, self.score1, self.score2, self.score3) stu = Student("20101120", "malong", 67, 77, 88) print(stu.returnMe()) stu.score1 = -23
很明显地,它们的代码被完整地复用了。这里score一、score二、score3被描述符类Score托管了,这3个分值分别被放进了Score实例对象的dict中(是单独存放它们仍是使用dict数据结构来保存,取决于你)。
另外,上面使用了弱引用的字典,由于每一个属性只在描述符对象中才会被用上,为了保证Student对象被销毁的时候能释放这些资源,因此采用弱引用,避免出现内存泄漏。