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
语句也是类似的调用过程。