zoukankan      html  css  js  c++  java
  • Python魔法方法之属性访问 ( __getattr__, __getattribute__, __setattr__, __delattr__ )

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

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

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

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

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

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

     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的规定无效吗?

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

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

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

    ② 去实例对象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__

    哈哈,酱紫也可以哦 ^v^

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

    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 called
    10 >>> t.x=2
    11 __setattr__() been called

    OK,至此,关于属性操作的问题暂时完结吧!

  • 相关阅读:
    HashMap是无序的
    mysql随笔
    visual stdio 安装OpenGL库文件
    myeclipse解决JSP文件里script背景颜色的调整
    js的鼠标事件整理-------Day47
    Linux环境编程之IPC进程间通信(五):Posix消息队列1
    HDFS 读取、写入、遍历文件夹获取文件全路径、append
    Appfuse搭建过程(下源代码不须要maven,lib直接就在项目里(否则痛苦死!))
    CSS样式命名规则
    关于c++ list容器的操作摸索
  • 原文地址:https://www.cnblogs.com/Jimmy1988/p/6804095.html
Copyright © 2011-2022 走看看