起步
在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作。例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问。一般而言,点(.)属性运算符比较直观,也是我们经常碰到的一种属性访问方式。
python的提供一系列和属性访问有关的特殊方法: __get__ , __getattr__ , __getattribute__ , __getitem__ 。本文阐述它们的区别和用法。
属性的访问机制
一般情况下,属性访问的默认行为是从对象的字典中获取,并当获取不到时会沿着一定的查找链进行查找。例如 a.x 的查找链就是,从 a.__dict__['x'] ,然后是 type(a).__dict__['x'] ,再通过 type(a) 的基类开始查找。
若查找链都获取不到属性,则抛出 AttributeError 异常。
__getattr__ 方法
__getattr__函数的作用: 如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。
这个方法是当对象的属性不存在是调用。如果通过正常的机制能找到对象属性的话,不会调用 __getattr__ 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class A: a = 1 def __getattr__( self , item): print ( '__getattr__ call' ) return item t = A() print (t.a) print (t.b) # output 1 __getattr__ call b |
__getattribute__ 方法
这个方法会被无条件调用。不管属性存不存在。如果类中还定义了 __getattr__ ,则不会调用 __getattr__() 方法,除非在 __getattribute__ 方法中显示调用 __getattr__() 或者抛出了 AttributeError 。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class A: a = 1 def __getattribute__( self , item): print ( '__getattribute__ call' ) raise AttributeError def __getattr__( self , item): print ( '__getattr__ call' ) return item t = A() print (t.a) print (t.b) |
所以一般情况下,为了保留 __getattr__ 的作用, __getattribute__() 方法中一般返回父类的同名方法:
1
2
|
def __getattribute__( self , item): return object .__getattribute__( self , item) |
使用基类的方法来获取属性能避免在方法中出现无限递归的情况。
__get__ 方法
这个方法比较简单说明,它与前面的关系不大。
如果一个类中定义了 __get__() , __set__() 或 __delete__() 中的任何方法。则这个类的对象称为描述符。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Descri( object ): def __get__( self , obj, type = None ): print ( "call get" ) def __set__( self , obj, value): print ( "call set" ) class A( object ): x = Descri() a = A() a.__dict__[ 'x' ] = 1 # 不会调用 __get__ a.x # 调用 __get__ |
如果查找的属性是在描述符对象中,则这个描述符会覆盖上文说的属性访问机制,体现在查找链的不同,而这个行文也会因为调用的不同而稍有不一样:
- 如果调用是对象实例(题目中的调用方式), a.x 则转换为调用: 。 type(a).__dict__['x'].__get__(a, type(a))
- 如果调用的是类属性, A.x 则转换为: A.__dict__['x'].__get__(None, A)
- 其他情况见文末参考资料的文档
__getitem__ 方法
这个调用也属于无条件调用,这点与 __getattribute__ 一致。区别在于 __getitem__ 让类实例允许 [] 运算,可以这样理解:
- __getattribute__ 适用于所有 . 运算符;
- __getitem__ 适用于所有 [] 运算符。
1
2
3
4
5
6
7
8
9
10
|
class A( object ): a = 1 def __getitem__( self , item): print ( '__getitem__ call' ) return item t = A() print (t[ 'a' ]) print (t[ 'b' ]) |
如果仅仅想要对象能够通过 [] 获取对象属性可以简单的:
1
2
|
def __getitem( self , item): return object .__getattribute__( self , item) |
总结
当这几个方法同时出现可能就会扰乱你了。我在网上看到一份示例还不错,稍微改了下:
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
27
28
29
30
31
32
33
34
|
class C( object ): a = 'abc' def __getattribute__( self , * args, * * kwargs): print ( "__getattribute__() is called" ) return object .__getattribute__( self , * args, * * kwargs) # return "haha" def __getattr__( self , name): print ( "__getattr__() is called " ) return name + " from getattr" def __get__( self , instance, owner): print ( "__get__() is called" , instance, owner) return self def __getitem__( self , item): print ( '__getitem__ call' ) return object .__getattribute__( self , item) def foo( self , x): print (x) class C2( object ): d = C() if __name__ = = '__main__' : c = C() c2 = C2() print (c.a) print (c.zzzzzzzz) c2.d print (c2.d.a) print (c[ 'a' ]) |
可以结合输出慢慢理解,这里还没涉及继承关系呢。总之,每个以 __ get 为前缀的方法都是获取对象内部数据的钩子,名称不一样,用途也存在较大的差异,只有在实践中理解它们,才能真正掌握它们的用法。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.hongweipeng.com/index.php/archives/1625/