鱼C论坛

 找回密码
 立即注册
查看: 2631|回复: 5

[学习笔记] 【045讲心得】【Python魔法方法之属性访问细探】

[复制链接]
发表于 2019-1-18 19:02:39 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 heidern0612 于 2019-1-18 19:05 编辑

写心得的过程都是自我思考的过程,借鉴了很多互联网大佬和鱼C论坛前辈的经验,仓促间难免有错,如有错误,恳请指出,不胜感激。

本文摘选自:戳我前进


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

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

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

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

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

为了验证以上,现列出代码如下:
class Test:
    def __getattr__(self, name):
        print('__getattr__')
 
    def __getattribute__(self, name):
        print('__getattribute__')
 
    def __setattr__(self, name, value):
        print('__setattr__')
 
    def __delattr__(self, name):
        print('__delattr__')

运行结果如下:
>>>t=Test()
>>> t.x
__getattribute__


如上述代码所示,x并不是Test类实例t的一个属性,首先去调用 __getattribute__() 方法,得知该属性并不属于该实例对象;

但是,按照常理,t.x应该打印 __getattribute__ 和__getattr__,但实际情况并非如此,为什么呢?难道以上Python的规定无效吗?



python中实例对象属性寻找的顺序如下:

① 首先访问 __getattribute__() 魔法方法(隐含默认调用,无论何种情况,均会调用此方法)

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

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

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

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

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



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

问题就出在了步骤的第④步,因为,一旦重载了 __getattribute__() 方法,如果找不到属性,则必须要手动加入第④步,否则无法进入到 第⑤步 (__getattr__)的

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


方法一:采用object(所有类的基类)
class Test:
    def __getattr__(self, name):
        print('__getattr__')
 
    def __getattribute__(self, name):
        print('__getattribute__')
        object.__getattribute__(self, name)
 
    def __setattr__(self, name, value):
        print('__setattr__')
 
    def __delattr__(self, name):
        print('__delattr__')

测试运行结果如下:
>>> t=Test()
>>> t.x
__getattribute__
__getattr_


方法二:采用 super() 方法

class Test:
    def __getattr__(self, name):
        print('__getattr__')
 
    def __getattribute__(self, name):
        print('__getattribute__')
        super().__getattribute__(name)
 
    def __setattr__(self, name, value):
        print('__setattr__')
 
    def __delattr__(self, name):
        print('__delattr__')

测试运行结果如下:
>>> t=Test()
>>> t.x
__getattribute__
__getattr__

以上介绍完毕,那么 __setattr__ 和 __delattr__ 方法相对简单了多了:
class Test:
    def __getattr__(self, name):
        print('__getattr__')
 
    def __getattribute__(self, name):
        print('__getattribute__')
        object.__getattribute__(self, name)
 
    def __setattr__(self, name, value):
        print('__setattr__')
 
    def __delattr__(self, name):
        print('__delattr__')


测试运行结果如下:
>>> t=Test()
>>> t.x=10
__setattr__
>>> del t.x
__delattr__


再补充一点:
class Test:
    def __init__(self):
        self.count = 0
    def __setattr__(self, name, value):
        print('__setattr__')
        self.count += 1

        
测试运行结果如下:
>>> t=Test()
__setattr__
Traceback (most recent call last):
  File "<pyshell#364>", line 1, in <module>
    t=Test()
  File "<pyshell#363>", line 3, in __init__
    self.count = 0
  File "<pyshell#363>", line 6, in __setattr__
    self.count += 1
AttributeError: 'Test' object has no attribute 'count'


为什么会出现上述情况呢?我还没有调用__setattr__()呢,只是单纯的定义了一个实例而已。

看报错信息很容易明白,这是因为:

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

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

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

④ 当执行 self.cout 加 1 操作时,将会去寻找 count 这个属性,然而,由于此时 __init__尚未完成,并不存在 count这个属性,因此导致 'AttributeError' 错误。


那么该如何更改呢?如下:
class Test:
    def __init__(self):
        self.count = 0
    def __setattr__(self, name, value):
        print('__setattr__')
        super().__setattr__(name, value+1)


测试运行结果如下:
 
        
>>> t=Test()
__setattr__
>>> t.count
1


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

再次将代码改进一下,如下:
class Test:
    def __setattr__(self, name, value):
        self.name = value


测试运行结果如下:
>>> t=Test()
>>> t.x=1
Traceback (most recent call last):
  File "<pyshell#413>", line 1, in <module>
    t.x=1
  File "<pyshell#411>", line 3, in __setattr__
    self.name = value
  File "<pyshell#411>", line 3, in __setattr__
    self.name = value
  File "<pyshell#411>", line 3, in __setattr__
    self.name = value
  [Previous line repeated 327 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object


这也就是老师课堂上讲的新手容易犯的无限递归错误。

原因很简单:当我们给 t.x 赋值时,调用了 __setattr__()方法,进入该方法;该方法中,又来了一次赋值(self.name = value),又会去调用 __setattr__() 方法,持续这个死循环;

Python解释器规定,递归深度不得超过200(可自行设置,在佳宇老师讲递归那一课中提到了如何设置递归深度),假如你超过了,不好意思,不带你玩了!

解决上述问题的方法也比较简单,用父类写好的__setattr__。
t.x=2
>>> class Test:
    def __setattr__(self, name, value):
        print('__setattr__() been called')
        super().__setattr__(name, value)

测试结果运行如下:
 
>>> t=Test()
>>> t.x=1
__setattr__() been called
>>> t.x=2
__setattr__() been called


也就是说,魔法方法很方便,它可以说是面向对象的python的一切,但,只要有相应的行为,就会触发相应的魔法方法,这也会导致有的重写魔法方法形式不对,会陷入死循环。

破解的方法除了上述调用系统写好的基类,老师还提到一种:

用到一个对象的特殊属性:__dict__它的作用是以字典的属性显示出当前对象的所有属性以及对应的值!比如:{'b': 1}
def __setattr__(self,name,value):

    self.__dict__[name] = value



同理,__getattribute__:是定义该属性被访问时的行为,__delattr__:定义属性被删除时的行为。

按照上面方式重写魔法方法时,__getattribute__会一直获得,__delattr__会一直删除而陷入死循环

而他们的解决方法,和上面写的一样,有两种方法,都可以选择使用。

使用魔法方法时,我们要了解他们的内在逻辑,才能更好地使用。



本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2019-1-19 10:32:37 | 显示全部楼层
貌似搞定了哈
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-1-19 10:35:29 | 显示全部楼层

谢谢大佬,凌晨时候佳宇老师给通过了。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-1-19 10:36:24 | 显示全部楼层
heidern0612 发表于 2019-1-19 10:35
谢谢大佬,凌晨时候佳宇老师给通过了。

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-2-16 16:19:25 | 显示全部楼层
学习的时候遇到一点问题,求大佬解释一下:
小甲鱼老师在45讲中,__setattr__中如果name是square,那么他的赋值为什么不会在调用__setattr__的时候进入无线循环呢?,就是上课的时候他写的程序只修改了else情况下的赋值方法,为什么就解决了问题?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-3-18 17:27:56 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-22 07:42

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表