python中__get__,__getattr__,__getattribute__的区别 深入了解及应用

__get__,__getattr__和__getattribute都是访问属性的方法,但不太相同。 
object.__getattr__(self, name) 
当一般位置找不到attribute的时候,会调用getattr,返回一个值或AttributeError异常。 

object.__getattribute__(self, name) 
无条件被调用,通过实例访问属性。如果class中定义了__getattr__(),则__getattr__()不会被调用(除非显示调用或引发AttributeError异常) 

object.__get__(self, instance, owner) 
如果class定义了它,则这个class就可以称为descriptor。owner是所有者的类,instance是访问descriptor的实例,如果不是通过实例访问,而是通过类访问的话,instance则为None。(descriptor的实例自己访问自己是不会触发__get__,而会触发__call__,只有descriptor作为其它类的属性才有意义。)(所以下文的d是作为C2的一个属性被调用) 

Python代码   收藏代码
  1. class C(object):  
  2.     a = 'abc'  
  3.     def __getattribute__(self, *args, **kwargs):  
  4.         print("__getattribute__() is called")  
  5.         return object.__getattribute__(self, *args, **kwargs)  
  6. #        return "haha"  
  7.     def __getattr__(self, name):  
  8.         print("__getattr__() is called ")  
  9.         return name + " from getattr"  
  10.       
  11.     def __get__(self, instance, owner):  
  12.         print("__get__() is called", instance, owner)  
  13.         return self  
  14.       
  15.     def foo(self, x):  
  16.         print(x)  
  17.   
  18. class C2(object):  
  19.     d = C()  
  20. if __name__ == '__main__':  
  21.     c = C()  
  22.     c2 = C2()  
  23.     print(c.a)  
  24.     print(c.zzzzzzzz)  
  25.     c2.d  
  26.     print(c2.d.a)  


输出结果是: 
Python代码   收藏代码
  1. __getattribute__() is called  
  2. abc  
  3. __getattribute__() is called  
  4. __getattr__() is called   
  5. zzzzzzzz from getattr  
  6. __get__() is called <__main__.C2 object at 0x16d2310> <class '__main__.C2'>  
  7. __get__() is called <__main__.C2 object at 0x16d2310> <class '__main__.C2'>  
  8. __getattribute__() is called  
  9. abc  


小结:可以看出,每次通过 实例 访问属性,都会经过__getattribute__函数。而当属性不存在时,仍然需要访问__getattribute__,不过接着要访问__getattr__。这就好像是一个异常处理函数。 
每次访问descriptor(即实现了__get__的类),都会先经过__get__函数。 

需要注意的是,当使用类访问不存在的变量是,不会经过__getattr__函数。而descriptor不存在此问题,只是把instance标识为none而已。

来看看如何自定义实现gettattr让代码更加的动态优雅:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 例子在原来的基础上简化了一下,排除依赖和干扰,详细参见原项目
class UrlGenerator( object ):
   def __init__( self , root_url):
     self .url = root_url
 
   def __getattr__( self , item):
     if item = = 'get' or item = = 'post' :
       print self .url
     return UrlGenerator( '{}/{}' . format ( self .url, item))
 
 
url_gen = UrlGenerator( 'http://xxxx' )
url_gen.users.show.get
 
>>> http: / / xxxx / users / show

充分利用getattr会在没有查找到相应实例属性时被调用的特点,方便的通过链式调用生成对应的url,源代码中在碰到http method的时候返回一个
可调用的对象更加的优雅,链式的操作不仅优雅而且还能很好的说明调用的接口的意义(restful的接口啦)。

示例
1.__getattr__示例: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
class Test( object ):
   def __init__( self ,name):
     self .name = name
   def __getattr__( self , value):
     if value = = 'address' :
       return 'China'
 
if __name__ = = "__main__" :
   test = Test( 'letian' )
   print test.name
   print test.address
   test.address = 'Anhui'
   print test.address

运行结果: 

?
1
2
3
letian
China
Anhui

如果是调用了一个类中未定义的方法,则__getattr__也要返回一个方法,例如: 

?
1
2
3
4
5
6
7
8
9
class Test( object ):
   def __init__( self ,name):
     self .name = name
   def __getattr__( self , value):
     return len
 
if __name__ = = "__main__" :
   test = Test( 'letian' )
   print test.getlength( 'letian' )

运行结果: 
6

2.__getattribute__示例: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test( object ):
   def __init__( self ,name):
     self .name = name
   def __getattribute__( self , value):
     if value = = 'address' :
       return 'China'
     
 
if __name__ = = "__main__" :
   test = Test( 'letian' )
   print test.name
   print test.address
   test.address = 'Anhui'
   print test.address

运行结果: 

?
1
2
3
None
China
China

深入思考
既然能通过定制类的getattr自定义方法来实现一些优雅的功能,自然我们也要对它有一些了解,包括和它相似的自定义方法getattribute

1. 用作实例属性的获取和拦截
当访问某个实例属性时, getattribute会被无条件调用,如未实现自己的getattr方法,会抛出AttributeError提示找不到这个属性,如果自定义了自己getattr方法的话,方法会在这种找不到属性的情况下被调用,比如上面的例子中的情况。所以在找不到属性的情况下通过实现自定义的getattr方法来实现一些功能是一个不错的方式,因为它不会像getattribute方法每次都会调用可能会影响一些正常情况下的属性访问:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
class Test( object ):
   def __init__( self , p):
     self .p = p
 
   def __getattr__( self , item):
     return 'default'
 
t = Test( 'p1' )
print t.p
print t.p2
 
>>> p1
>>> default

2. 自定义getattribute的时候防止无限递归
因为getattribute在访问属性的时候一直会被调用,自定义的getattribute方法里面同时需要返回相应的属性,通过self.__dict__取值会继续向下调用getattribute,造成循环调用:

?
1
2
3
4
5
6
7
8
9
class AboutAttr( object ):
   def __init__( self , name):
     self .name = name
 
   def __getattribute__( self , item):
     try :
       return super (AboutAttr, self ).__getattribute__(item)
     except KeyError:
       return 'default'

这里通过调用绑定的super对象来获取队形的属性,对新式类来说其实和object.__getattribute__(self, item)一样的道理:

默认情况下自定义的类会从object继承getattribute方法,对于属性的查找是完全能用的
getattribute的实现感觉还是挺抽象化的,只需要绑定相应的实例对象和要查找的属性名称就行
3.同时覆盖掉getattribute和getattr的时候,在getattribute中需要模仿原本的行为抛出AttributeError或者手动调用getattr

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class AboutAttr( object ):
   def __init__( self , name):
     self .name = name
 
   def __getattribute__( self , item):
     try :
       return super (AboutAttr, self ).__getattribute__(item)
     except KeyError:
       return 'default'
     except AttributeError as ex:
       print ex
 
   def __getattr__( self , item):
     return 'default'
 
at = AboutAttr( 'test' )
print at.name
print at.not_exised
 
>>>test
>>> 'AboutAttr' object has no attribute 'not_exised'
>>> None

猜你喜欢

转载自blog.csdn.net/weixin_40907382/article/details/80340415