首頁 > 軟體

一文解密Python中_getattr_和_getattribute_的用法與區別

2023-01-14 14:01:28

__getattr__

當存取範例物件的某個不存在的屬性時,毫無疑問會報錯,會丟擲 AttributeError。

class A:
    pass

a = A()
a.xxx
"""
AttributeError: 'A' object has no attribute 'xxx'
"""

但如果我們希望在找不到某個屬性時,不要報錯,而是返回預設值,該怎麼做呢?這個時候我們就需要定義 __getattr__ 方法了,當範例物件找不到某個屬性時會執行此方法。

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def get_info(self):
        return f"name: {self.name}, age: {self.age}"

    def __getattr__(self, item):
        return f"你存取了 {item} 屬性"


girl = Girl()
print(girl.name, girl.age)  # 古明地覺 17
print(girl.get_info)  # <bound method Girl.get_info...>
print(girl.get_info())  # name: 古明地覺, age: 17

print(girl.xxx)  # 你存取了 xxx 屬性
print(girl.yyy)  # 你存取了 yyy 屬性
print(girl.zzz)  # 你存取了 zzz 屬性

所以非常簡單,就是當範例物件存取了一個不存在的屬性時,會執行 __getattr__ 方法。當然,如果屬性存在的話,就不會執行了,而是返回相應的值。

此外 __getattr__ 還有一個用法,就是在模組匯入的時候。假設我們有一個 tools.py,裡面程式碼如下:

def __getattr__(name):
    return f"{__name__} 中不存在 {name}"

name = "古明地覺"
age = 17

相信你明白它是幹什麼的了,我們來匯入它:

from tools import name, age, xxx, yyy

print(name, age)  # 古明地覺 17
print(xxx)  # tools 中不存在 xxx
print(yyy)  # tools 中不存在 yyy

import tools
print(tools.zzz)  # tools 中不存在 zzz

在獲取 tools.py 裡面的屬性時,如果不存在,那麼同樣會去執行 __getattr__,應該還是很簡單的。

__getattribute__

__getattribute__ 被稱為屬性攔截器,它比 __getattr__ 要霸道的多,這兩者的區別如下:

  • __getattr__:當存取的屬性不存在時,才會執行此方法;
  • __getattribute__:不管存取的屬性是否存在,一律執行此方法;

我們舉個例子:

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def __getattribute__(self, item):
        return f"獲取屬性: {item}"


girl = Girl()
print(girl.name)  # 獲取屬性: name
print(girl.age)  # 獲取屬性: age
print(girl.xxx)  # 獲取屬性: xxx

# 即便你想通過屬性字典獲取也是沒有用的
# 因為不管什麼屬性,都會執行 __getattribute__
print(girl.__dict__)  # 獲取屬性: __dict__

並且在使用這個方法的時候,一定要謹慎,因為你一不小心就會陷入無限遞迴。

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def __getattribute__(self, item):
        return getattr(self, item)


girl = Girl()
print(girl.name)
# 顯然上面的程式碼會陷入無限遞迴
# 因為 girl.name 會呼叫 __getattribute__
# 而在裡面又執行了 getattr(self, item),還是在獲取屬性
# 所以又會呼叫 __getattribute__,於是會無限遞迴

# 可能有人說,那我換一種方式
# 我將 getattr(self, item) 改成 self.__dict__[item] 可以嗎
# 答案也是不行的,因為 self.__dict__ 仍是在獲取屬性
# 只要獲取屬性,就會觸發 __getattribute__,依舊會陷入無限遞迴

所以 __getattribute__ 非常霸道,那麼我們如何使用它呢?答案是通過父類別。

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def __getattribute__(self, item):
        return super().__getattribute__(item)


girl = Girl()
print(girl.name)
print(girl.age)
try:
    girl.xxx
except AttributeError:
    print("屬性 xxx 不存在")
"""
古明地覺
17
屬性 xxx 不存在
"""

當我們呼叫父類別的 __getattribute__ 時,如果屬性存在,它會直接返回;如果範例沒有該屬性,那麼會檢測我們是否定義了 __getattr__,定義了則執行,沒定義則丟擲 AttributeError。我們將這兩個方法結合起來,看一個例子:

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def __getattr__(self, item):
        print(f"__getattr__ {item}")
        return f"獲取屬性 {item}"

    def __getattribute__(self, item):
        print(f"__getattribute__ {item}")
        return super().__getattribute__(item)


girl = Girl()
# 不管屬性是否存在,一律呼叫 __getattribute__
# 然後在裡面我們又呼叫了父類別的 __getattribute__
# 那麼會檢測屬性是否存在,存在則直接獲取對應的值,然後返回
print(girl.name)
"""
__getattribute__ name
古明地覺
"""
# age 也是相同的邏輯,和 name 一樣,這兩個屬性都是存在的
print(girl.age)
"""
__getattribute__ age
17
"""

# 依舊執行 __getattribute__,然後呼叫父類別的 __getattribute__
# 由於屬性 xxx 不存在,於是會執行 __getattr__
print(girl.xxx)
"""
__getattribute__ xxx
__getattr__ xxx
獲取屬性 xxx
"""

那麼問題來了,這個 __getattribute__ 有啥用呢?該方法被稱為屬性攔截器,顯然它可以起到一個控制屬性存取許可權的作用。

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def __getattr__(self, item):
        return f"屬性 {item} 不存在"

    def __getattribute__(self, item):
        if item == "age":
            return "女人芳齡不可洩露,別問,問就是還不到 18 歲"
        return super().__getattribute__(item)


girl = Girl()
# name 屬性存在,所以在 __getattribute__ 中直接返回
print(girl.name)
"""
古明地覺
"""
# age 也是如此,也是在 __getattribute__ 中直接返回
# 只不過它相當於被攔截了
print(girl.age)
"""
女人芳齡不可洩露,別問,問就是還不到 18 歲
"""
# 父類別在執行 __getattribute__ 的時候,發現 xxx 屬性不存在
# 於是會觸發 __getattr__ 的執行(如果沒定義則丟擲 AttributeError)
print(girl.xxx)
"""
屬性 xxx 不存在
"""

所以 __getattribute__ 就相當於一個屬性攔截器,不管獲取啥屬性,都要先經過它。如果你發現有一些屬性不想讓外界存取,那麼直接攔截掉即可,比如上面程式碼中的 age 屬性。

然後對於那些可以讓外界存取的屬性,則需要呼叫父類別的 __getattribute__ 幫我們去獲取(因為我們手動獲取的話會陷入無線遞迴),並且在獲取不存在的屬性時也會自動執行 __getattr__。

當然啦,除了屬性,方法也是一樣的。

class Girl:

    def __init__(self):
        self.name = "古明地覺"
        self.age = 17

    def get_info(self):
        return f"name: {self.name}, age: {self.age}"

    def __getattribute__(self, item):
        if item == "get_info":
            return "此方法禁止獲取"
        return super().__getattribute__(item)


girl = Girl()
print(girl.get_info)
"""
此方法禁止獲取
"""
# 預設情況下 girl.get_info 拿到的是一個方法
# 然後再加上小括號就會執行該方法
# 但在 __getattribute__ 中我們將其攔截了,並返回一個字串
# 所以此時 girl.get_info() 就會報錯,因為字串無法被呼叫

以上內容就是 __getattr__ 和 __getattribute__ 的區別與用法,在工作中看看能不能讓它們派上用場。不過說實話,__getattr__ 用的還是蠻頻繁的,而 __getattribute__ 則用的不多,至少我就很少用。

到此這篇關於一文解密Python中_getattr_和_getattribute_的用法與區別的文章就介紹到這了,更多相關Python getattr getattribute內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com