python的property实现原理

python学习笔记之property的实现原理

以实际的例子来说明,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Property:
def __init__(self,fget=None,fset=None,fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel

def __get__(self,instance,cls):
if self.fget is not None:
return self.fget(instance)

def __set__(self,instance,value):
if self.fset is not None:
self.fset(instance,value)

def __delete__(self,instance):
if self.fdel is not None:
self.fdel(instance)

def getter(self,fn):
self.fget = fn

def setter(self,fn):
self.fset = fn

def deler(self,fn):
self.fdel = fn
1
2
3
4
5
6
7
8
9
10
11
class Spam:
def __init__(self,val):
self.__val = val

@Property
def val(self):
return self.__val

@val.setter
def set_val(self,value):
self.__val = value
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
2
3
def __get__(self,instance,cls):
if self.fget is not None:
return self.fget(instance)

这里的instance指向Spam类的实例scls指向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的是如何实现时,又需要把两个函数的名称设置成不同,比如上边的valset_val,这是为什么?

既然可以对一个类的实例变量做装饰,那对类变量也是一样的,如下边代码:

1
2
3
4
5
6
class ClassProperty:
def __init__(self,fn):
self.fn = fn

def __get__(self,instance,cls):
return self.fn(cls)
1
2
3
4
5
6
7
8
9
10
class Spam1:
__val = 3

@ClassProperty
def val(cls):
return cls.__val

@ClassProperty
def name(cls):
return cls.__name__.lower()
1
s1 = Spam1()
1
s1.val
3
1
s1.name
'spam1'

分析一下上边的代码:

当python解释器执行到下边代码块时

1
2
3
4
5
6
7
8
9
10
class Spam1:
__val = 3

@ClassProperty
def val(cls):
return cls.__val

@ClassProperty
def name(cls):
return cls.__name__.lower()

解释器会创建一个名为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语句也是类似的调用过程。

文章目录
|