Python魔法方法:getattr, getattribute, setattr, delattr

一般状况下,咱们在访问类或者实例对象的时候,会牵扯到一些属性访问的魔法方法,主要包括:shell

① __getattr__(self, name): 访问不存在的属性时调用ide

② __getattribute__(self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)spa

③ __setattr__(self, name, value):设置实例对象的一个新的属性时调用.net

④ __delattr__(self, name):删除一个实例对象的属性时调用code

为了验证以上,现列出代码以下:orm

 1 class Test: 
 2     def __getattr__(self, name): 
 3         print('__getattr__') 
 4  
 5    def __getattribute__(self, name): 
 6         print('__getattribute__') 
 7  
 8     def __setattr__(self, name, value): 
 9         print('__setattr__')
 10 
 11     def __delattr__(self, name):
 12         print('__delattr__')
 13 
 14 >>> t=Test()
 15 >>> t.x
 16 __getattribute__

如上述代码所示,x并非Test类实例t的一个属性,首先去调用 __getattribute__() 方法,得知该属性并不属于该实例对象;可是,按照常理,t.x应该打印 __getattribute__ 和__getattr__,但实际状况并不是如此,为何呢?难道以上Python的规定无效吗?对象

不要着急,听我慢慢道来!blog

 实例对象属性寻找的顺序以下:递归

① 首先访问 __getattribute__() 魔法方法(隐含默认调用,不管何种状况,均会调用此方法)ci

② 去实例对象t中查找是否具有该属性: t.__dict__ 中查找,每一个类和实例对象都有一个 __dict__ 的属性

③ 若在 t.__dict__ 中找不到对应的属性, 则去该实例的类中寻找,即 t.__class__.__dict__

④ 若在实例的类中也招不到该属性,则去父类中寻找,即 t.__class__.__bases__.__dict__中寻找

⑤ 若以上均没法找到,则会调用 __getattr__ 方法,执行内部的命令(若未重载 __getattr__ 方法,则直接报错:AttributeError)

以上几个流程,即完成了属性的寻找。

 

可是,以上的说法,并不能解释为何执行 t.x 时,不打印 ’__getattr__'  啊?

你看,又急了不是,做为一名程序猿,必定要有耐心 ^_^

问题就出在了步骤的第④步,由于,一旦重载了 __getattribute__() 方法,若是找不到属性,则必需要手动加入第④步,不然没法进入到 第⑤步 (__getattr__)的。

验证一下以上说法是否正确:

方法一:采用object(全部类的基类)


 1 class Test: 
 2     def __getattr__(self, name): 
 3         print('__getattr__') 
 4  
 5     def __getattribute__(self, name): 
 6         print('__getattribute__') 
 7         object.__getattribute__(self, name) 
 8  
 9     def __setattr__(self, name, value):
 10         print('__setattr__')
 11 
 12     def __delattr__(self, name):
 13         print('__delattr__')
 14 
 15         
 16 >>> t=Test()
 17 >>> t.x
 18 __getattribute__
 19 __getattr__

 

方法二:采用 super() 方法

 1 class Test: 
 2     def __getattr__(self, name): 
 3         print('__getattr__') 
 4  
 5     def __getattribute__(self, name): 
 6         print('__getattribute__') 
 7         super().__getattribute__(name) 
 8  
 9     def __setattr__(self, name, value):
 10         print('__setattr__')
 11 
 12     def __delattr__(self, name):
 13         print('__delattr__')
 14 
 15         
 16 >>> t=Test()
 17 >>> t.x
 18 __getattribute__
 19 __getattr__


那么方法一和方法二有什么不一样呢?仔细看一下你就会发现,其实就是很小的一点不一样而已:


1 #方法一:使用基类object的方法
2 def __getattribute__(self, name):
3     print('__getattribute__')
4     object.__getattribute__(self, name)
5 
6 #方法二:使用super()方法(有的认为super()是类,此处暂以方法处理)
7 def __getattribute__(self, name):
8         print('__getattribute__')
9         super().__getattribute__(name)


 

在Python2.x中,以上super的用法应该改成 super(Test, self).xxx,但3.x中,能够像上面代码同样简单使用。

那么super究竟是什么东西呢?若是想详细的理解,请点击这里

 

哈哈,以上介绍完毕,那么 __setattr__ 和 __delattr__ 方法相对简单了多了:

1 class Test:

2     def __getattr__(self, name):

3         print('__getattr__')

4  

5     def __getattribute__(self, name):

6         print('__getattribute__')

7         object.__getattribute__(self, name)

8  

9     def __setattr__(self, name, value):

10         print('__setattr__')

11

12     def __delattr__(self, name):

13         print('__delattr__')

14

15        

16 >>> t=Test()

17 >>> t.x=10

18 __setattr__

19 >>> del t.x

20 __delattr__

 

至此,关于属性访问,先告一段落,后续会有更高级的descirptor。

 

对了,再补充一点哈!

 1 class Test: 
 2     def __init__(self): 
 3         self.count = 0 
 4     def __setattr__(self, name, value): 
 5         print('__setattr__') 
 6         self.count += 1 
 7  
 8          
 9 >>> t=Test()
 10 __setattr__
 11 Traceback (most recent call last):
 12   File "<pyshell#364>", line 1, in <module>
 13     t=Test()
 14   File "<pyshell#363>", line 3, in __init__
 15     self.count = 0
 16   File "<pyshell#363>", line 6, in __setattr__
 17     self.count += 1
 18 AttributeError: 'Test' object has no attribute 'count'

 

为何会出现上述状况呢?我尚未调用__setattr__()呢,只是单纯的定义了一个实例而已? @_@(咋回事?幻觉吗)

看报错信息很容易明白,这是由于:

① __init__()时,给内部属性 self.count进行了赋值;

② 赋值默认调用 __setattr__() 方法

③ 当调用 __setattr__()方法时,首先打印 '__setattr__'字符串,然后执行 self.cout += 1操做

④ 当执行 self.cout 加 1 操做时,将会去寻找 count 这个属性,然而,因为此时 __init__还没有完成,并不存在 count这个属性,所以致使 'AttributeError' 错误

那么该如何更改呢?能够这样的:


 1 class Test: 
 2     def __init__(self): 
 3         self.count = 0 
 4     def __setattr__(self, name, value): 
 5         print('__setattr__') 
 6         super().__setattr__(name, value+1) 
 7  
 8          
 9 >>> t=Test()
 10 __setattr__
 11 >>> t.count
 12 1

 

如何,问题解决了吧!

可是以上代码虽然解决了报错的问题,深刻体会一下,你会发现,采用此方法只是给 基类object增长了一个属性 count,而并非实例的属性,所以,以上这种写法避免使用

 

另外,再次将代码改进一下,以下:


 1 class Test: 
 2     def __setattr__(self, name, value): 
 3         self.name = value 
 4  
 5          
 6 >>> t=Test() 
 7 >>> t.x=1 
 8 Traceback (most recent call last): 
 9   File "<pyshell#413>", line 1, in <module>
 10     t.x=1
 11   File "<pyshell#411>", line 3, in __setattr__
 12     self.name = value
 13   File "<pyshell#411>", line 3, in __setattr__
 14     self.name = value
 15   File "<pyshell#411>", line 3, in __setattr__
 16     self.name = value
 17   [Previous line repeated 327 more times]
 18 RecursionError: maximum recursion depth exceeded while calling a Python object

竟然报错了,看报错信息为 “递归错误”,我没用递归啊,怎么会有这个错误呢?

其实,缘由很简单:当咱们给 t.x 赋值时,调用了 __setattr__()方法,进入该方法;该方法中,又来了一次赋值(self.name = value),又会去调用 __setattr__() 方法,持续这个死循环(子子孙孙无穷尽也,必需要有一代断子绝孙);

咱们知道,系统的资源是有限的,丫的你总是申请资源不释放,系统哪来的那么多资源给你本身用?所以,Python解释器规定,递归深度不得超过200(不一样版本不同),你超过了,很差意思,不带你玩了!

因此,咱们只好改变上述的问题了:

 1 t.x=2 2 >>> class Test: 3     def __setattr__(self, name, value): 4         print('__setattr__() been called') 5         super().__setattr__(name, value) 6  7 >>> t=Test() 8 >>> t.x=1 9 __setattr__() been called10 >>> t.x=211 __setattr__() been called
相关文章
相关标签/搜索