python学习笔记之property的实现原理
以实际的例子来说明,如下:
1 | class Property: |
1 | class Spam: |
1 | s = Spam(4) |
1 | s.val #Spam.val.__get__(s,Spam) |
4
1 | s.val = 3 #Spam.val.__set__(s,3) |
1 | s.val |
3
现在来分析一下上边的代码:
当执行s = Spam(4)时,调用比较简单,就是直接执行类Spam的构建函数__init__,这样就得到一个私有变量__val,值为4。
当执行s.val时,因val函数被Property所装饰,所以val函数可以像属性一样调用;因Property类是一个描述器,所以s.val操作首先会实例化Property类,把__init__函数中的fget指向Spam类的val函数,把fset指向Spam类的set_val函数,接着就去调用Property类的__get__函数代码块,如下:
1 | def __get__(self,instance,cls): |
这里的instance指向Spam类的实例s,cls指向Spam类,因fget已指向了Spam类的val函数,所以if语句为True,执行return self.fget(instance)语句,这里的self.fget指向了Spam类的val函数,instance指向了Spam类的实例s,其实质就是调用Spam类的val函数,即返回__val的值。这个调用过程其实质与Spam.val.__get__(s,Spam)相同。
当执行s.val = 3时,整个调用过程与执行s.val的过程类似,只是把调用__get__换成了调用__set__函数,这个调用过程其实质与Spam.val__set__(s,3)相同。
有个疑问?
在2016-05-06-面向对象-封装-property.md中讲如何使用@property这个装饰器时说被@property装饰的函数名(FunName)与被"@FunName.setter"装饰器装饰的函数名要相同,但今天这里讲property的是如何实现时,又需要把两个函数的名称设置成不同,比如上边的val和set_val,这是为什么?
既然可以对一个类的实例变量做装饰,那对类变量也是一样的,如下边代码:
1 | class ClassProperty: |
1 | class Spam1: |
1 | s1 = Spam1() |
1 | s1.val |
3
1 | s1.name |
'spam1'
分析一下上边的代码:
当python解释器执行到下边代码块时
1 | class Spam1: |
解释器会创建一个名为Spam1的类空间,在此空间中创建一个__val的私有变量;当执行到第一个被@ClassProperty装饰器装饰的val函数时,会把val这个函数名称传递给ClassProperty类的fn参数,接着下边的函数name也会被传递给ClassProperty类的fn参数,这样函数val已不再是原先的函数了,而是一个被ClassProperty装饰过的函数。
当执行s1 = Spam1()时,只是实例化了一个类,比较好理解。
当执行s1.val时,是去调用ClassProperty类的__get__函数,把Spam1的实例s1传递给instance参数,把Spam1传递给cls参数,当执行return self.fn(cls)语句时其实质是执行val(cls),所以是返回一个函数,此时解释器回到Spam1类中的val函数,执行return cls.__val,所以就输出了3。
执行s1.name语句也是类似的调用过程。