【Python魔術(shù)方法】幾個特殊的__get*__方法

簡述

結(jié)合代碼區(qū)分一下python的幾個魔術(shù)方法__get__, __getattr__, __getattribute__, __getitem__

操作環(huán)境

win7_64 + Python 2.7.16 + IPython 5.9.0

__dict__

首先我們通過obj.attr的方式訪問對象屬性的時候,在python內(nèi)部,實際是訪問了obj.__dict__[attr],__dict__有文章單獨說明,這里只需要簡單理解,一般情況下,我們訪問一個對象的屬性,其實是在讀取這個屬性的__dict__字典:

In [7]: class Test(object):
   ...:     def __init__(self):
   ...:         self.a = 1
   ...:         self.b = 2
   ...:

# 定義了一個Test類的對象t
In [8]: t = Test()

# 雖然t沒有顯示定義__dict__屬性,但是python會自動生成,它是一個字典
In [9]: t.__dict__
Out[9]: {'a': 1, 'b': 2}

In [10]: t.a
Out[10]: 1

# t.a的訪問方式等價于t.__dict__['a']的訪問方式
In [11]: t.__dict__['a']
Out[11]: 1

__getattr__

如果一個類定義了此方法,當(dāng)訪問一個類不存在的屬性的時候,會訪問此方法并且返回對應(yīng)的值,如果不存在對應(yīng)的訪問,應(yīng)該拋出AttributeError

In [10]: class Test(object):
    ...:     def __init__(self):
    ...:         self.a = 1
    ...:
    ...:     def __getattr__(self, name): # name參數(shù)就是訪問的屬性
    ...:         print name
    ...:         return 2
    ...:
    ...: t = Test()
    ...:

In [11]: t.a
Out[11]: 1

In [12]: t.b
b
Out[12]: 2

In [13]: t.c
c
Out[13]: 2

t對象有a屬性,所以獲取到了對應(yīng)的值,但是bc均不是對象t的屬性,故訪問了__getattr__方法,參數(shù)name獲取到了對應(yīng)的屬性名,并且都返回2.

注意下面的代碼

class Test(object):
    def __init__(self):
        self.a = 1
        
    def __getattr__(self, name):
        return self.name
        
t = Test()
t.b

當(dāng)我們在訪問t.b的時候,t對象沒有b屬性,會訪問__getattr__,此方法再次通過self.name的方式嘗試訪問b屬性,還是不存在,同樣走到了__getattr__,這樣會造成無限循環(huán),直到最后拋出RuntimeError: maximum recursion depth exceeded異常,我們可以通過下面的方式來避免出現(xiàn)這種情況:

class Test(object):
    def __init__(self):
        self.a = 1
        
    def __getattr__(self, name):
        # 有對應(yīng)的屬性,我們才訪問它的屬性
        if hasattr(self, name):
            # getattr訪問屬性的方式和t.b的方式是一樣的
            return getattr(self, name)
        else:
            # 否則直接拋出異常
            raise AttributeError()
        
t = Test()
t.b

__getattribute__

只要定義了此函數(shù),任何屬性訪問,會無條件的調(diào)用此方法,也就是屏蔽了__dict____getattr__

In [10]: class Test(object):
    ...:     def __init__(self):
    ...:         self.a = 1
    ...:
    ...:     def __getattr__(self, name):
    ...:         print '__getattr__'
    ...:
    ...:     def __getattribute__(self, name):
    ...:         print '__getattribute__'
    ...:
    ...: t = Test()
    ...: t.b
    ...:
__getattribute__

# 同樣t.__dict__也是在訪問t的屬性,也訪問到了__getattribute__方法
In [11]: t.__dict__
__getattribute__
  1. 它和__getattr__一樣,如果不想有對應(yīng)的屬性,應(yīng)該拋出AttributeError,而且也需要注意遞歸訪問的情況出現(xiàn)
  2. 此方法只針對新式類有效,老式類此方法是不會生效的,除非主動調(diào)用

__getitem__

這個方法最簡單,定義此方法的類一般是模擬容器的行為,比如模擬字典,列表的訪問

In [12]: class Test(object):
    ...:     def __init__(self):
    ...:         self.a = 1
    ...:
    ...:     def __getitem__(self, key):
    ...:         print key
    ...:
    ...: t = Test()
    ...: t['a']
    ...: t[1]
    ...:
a
1
  1. 其中key在列表里面是整數(shù),在字典里面可以是字符串
  2. 如果key的類型不正確,應(yīng)該拋出TypeError
  3. 如果key索引錯誤拋出IndexError
  4. 如果key在字典不存在則拋出KeyError

__get__
此方法涉及到描述符的概念,由于篇幅較長,會單獨寫一篇文章記錄。

總結(jié)

  1. 新式類只要定義了__getattribute__,則會無條件訪問,但老式類不生效
  2. 定義了__getattr__,如果訪問的屬性不存在的時候,才會訪問此方法
  3. __getitem__最簡單,用于模擬容器的訪問方式
  4. __getattribute____getattr__都應(yīng)避免進(jìn)入遞歸調(diào)用,并且如果訪問的屬性不應(yīng)該存在的情況下,需要拋出AttributeError異常
  5. getattr(obj, attr)的方式訪問屬性和obj.attr的方式是一樣的,都等同于obj.__dict__[attr]

參考

  1. https://docs.python.org/2/reference/datamodel.html#object.getattr
  2. https://docs.python.org/2/reference/datamodel.html#object.getattribute
  3. https://docs.python.org/2/reference/datamodel.html#object.get
  4. https://docs.python.org/2/reference/datamodel.html#invoking-descriptors
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容