python的__get__方法看這一篇就足夠了


get類型函數

直接上代碼:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

if __name__ == '__main__':
    t = TestMain()
    print(t.a)

在沒有任何get函數的情況下很簡單,打印結果是:

TestMain:__init__
1

但是如果訪問一個不存在的屬性:

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b) # 訪問了一個不存在的屬性

結果是:

TestMain:__init__
Traceback (most recent call last):
1
  File "C:/Users/sk-leilin/Desktop/mpdp-code-master/test.py", line 19, in <module>
    print(t.b)
AttributeError: 'TestMain' object has no attribute 'b'

 

可以看見報錯了,下載我們來測試一下__getattr__函數:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)

打印結果是:

TestMain:__init__
1
TestMain:__getattr__
2

我們仍然訪問了一個本來不存在的t.b,為什么這里沒有報錯呢,因為我們定義了__getattr__函數,而且讓它直接返回了2,也就是說,如果定義了這個函數后,訪問不存在的屬性,會自動調用這個函數作為返回值。

接下來我們看一下__getattribute__這個函數:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        return 3

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)

 打印結果是:

TestMain:__init__
TestMain:__getattribute__
3
TestMain:__getattribute__
3

可以看到,無論是訪問存在的t.a還是不存在的t.b,都訪問到了__getattribute__這個函數,也就是說,只要定義了這個函數,那么屬性的訪問,都會走到這個函數里面。

我們在看下面的代碼:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        if item == 'c':
            raise AttributeError
        return 3

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)
    print(t.c)

 我們知道只要定義了__getattribute__函數,就肯定執行這個函數來獲取屬性,這次我們增加了判斷如果訪問c這個屬性,我們拋出異常,最后的結果是:

TestMain:__init__
TestMain:__getattribute__
3
TestMain:__getattribute__
3
TestMain:__getattribute__
TestMain:__getattr__
2

也就是說,如果__getattribute__拋出了AttributeError異常,那么會繼續訪問__getattr__函數的。

總結:

  1. 如果定義了__getattribute__,那么無論訪問什么屬性,都是通過這個函數獲取,包括方法,t.f()這種也是訪問的這個函數,此時這個函數應該放回一個方法,如果像例子中,仍然返回一個數字,你會獲得一個TypeError: 'int' object is not callable錯誤
  2. 只要定義了__getattribute__方法,不管你訪問一個存在的還是不存在的屬性,都由這個方法返回,比如訪問t.a,雖然a存在,但是只要定義了這個訪問,那么就不是訪問最開始的a了
  3. 如果__getattribute__拋出了AttributeError異常,並且定了了__getattr__函數,那么會調用__getattr__這個函數,不論這個屬性到底是不是存在
  4. 也就是說屬性訪問的一個大致優先級是:__getattribute__ > __getattr__ > __dict__

單獨說一說__get__函數

上面說了__getattribute____getattr__,這里單獨說一下__get__,因為這個涉及到其它的概念,就是描述器(Descriptor)。

一個類只要實現了__get____set____delete__中任意一個方法,我們就可以叫它描述器(descriptor)。如果只定義了__get__我們叫非資料描述器(non-data descriptor),如果__set____delete__任意一個/或者同時出現,我們叫資料描述器(data descriptor)。

首先明確一點,擁有這個方法的類,應該(也可以說是必須)產生一個實例,並且這個實例是另外一個類的類屬性(注意一定是類屬性,通過self的方式產生就不屬於__get__范疇了)。

也就是說擁有這個方法的類,那么它的實例應該屬於另外一個類/對象的一個屬性。 直接看代碼吧:

class TestDes:
    def __get__(self, instance, owner):
        print(instance, owner)
        return 'TestDes:__get__'

class TestMain:
    des = TestDes()

if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    print(TestMain.des)

其中TestDes定義了__get__方法,在TestMain中,定義了一個類屬性des,是TestDes的一個實例,我們訪問t.des或者TestMain.des的時候訪問的就是訪問了TestDes__get__方法。

打印結果是:

<__main__.TestMain object at 0x0000022563D5D3C8> <class '__main__.TestMain'>
TestDes:__get__
None <class '__main__.TestMain'>
TestDes:__get__

其中,__get__方法的第一個參數是實際擁有者的實例,如果沒有則為None,第二個參數是實際所屬的類。

看一下下面的代碼:

class TestDes:
    def __get__(self, instance, owner):
        print(instance, owner)
        return 'TestDes:__get__'

class TestMain:
    def __init__(self):
        self.des = TestDes()

if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    # print(TestMain.des) #很明顯這里會報錯

我們通過__init__來產生了一個實例的des屬性,這時候,print(t.des)訪問的就不是__get__函數了,實際打印結果是:

<__main__.TestDes object at 0x00000165A77ECCF8>

也就是當成一個普通的實例來處理的。

非資料描述器,也就是只有__get__,不管是類還是實例去訪問,默認都獲得的是__get__的返回值,但是,如果中間有任何一次重新賦值,那么,這個實例獲得的是新的值(對象),已經和原來的描述器完全脫離了關系
資料描述器,比如有__set__方法,后期通過實例對描述器進行賦值,那么訪問的是__set__,並且永遠關聯起來。但是如果通過修改類屬性的方式復制,那么也會被重新獲取新的值(對象)。

看下面的代碼:

class TestDes:
    def __get__(self, instance, owner):
        print('TestDes:__get__', instance, owner)
        return 'TestDes:__get__'

class TestMain:
    des = TestDes()

if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    print(TestMain.des)

    print()

    t.des = 1
    print(t.des)
    print(TestMain.des)

    print()

    TestMain.des = 1
    print(t.des)
    print(TestMain.des)

上面是一個非資料描述器,打印結果是:

TestDes:__get__ <__main__.TestMain object at 0x000002C9BCCF0080> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

1
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

1
1

具體根據上面的描述行為進行分析,就可以得出結果了。

我們在看一下資料描述器:

class TestDes:
    def __get__(self, instance, owner):
        print('TestDes:__get__', instance, owner)
        return 'TestDes:__get__'

    def __set__(self, instance, value):
        print('TestDes:__set__', instance, value)

# 其它代碼沒有修改

打印結果如下:

TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

TestDes:__set__ <__main__.TestMain object at 0x000002140A46D390> 1
TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__

1
1

 

總結

  1. __getattribute____getattr__用於實例訪問屬性使用,擁有__get__方法的類是只能其實例屬於類屬性的時候生效
  2. 只要有__getattribute__,任何屬性訪問都是這個的返回值,以下都是在__getattribute__不存在或者有AttributeError異常發生的情況下描述的
  3. 訪問不存在的屬性,__getattr__生效
  4. 訪問存在的屬性,如果是描述器,描述器生效
  5. 如果通過實例對描述器進行賦值操作,又有資料和非資料描述器的區分,如果定義了__set__,那么此方法生效,並且仍然是原始的資料描述器,否則被賦值為新對象
  6. 描述器賦值如果是通過類的屬性方式賦值,而不是類的實例方式賦值,描述器失效

針對描述器的說明: 描述器是被__getattribute__調用的,如果重寫了這個方法,將會阻止自動調用描述器,資料描述器總是覆蓋了實例的__dict__, 非資料描述器可能覆蓋實例的__dict__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM