首頁  >  文章  >  後端開發  >  Python魔法函數學習之__missing__

Python魔法函數學習之__missing__

WBOY
WBOY原創
2022-03-11 17:23:222223瀏覽

這篇文章為大家帶來了關於python的相關知識,其中主要介紹了「__missing__()」函數的相關問題,下面我們就一起來看一下這個魔法函數,希望對大家有幫助。

Python魔法函數學習之__missing__

推薦學習:python學習教學

1、有點價值的__missing__()

從普通的字典中取值時,可能會出現key 不存在的情況:

dd = {'name':'PythonCat'}
dd.get('age')        # 结果:None
dd.get('age', 18)    # 结果:18
dd['age']            # 报错 KeyError
dd.__getitem__('age')  # 等同于 dd['age']

Python魔法函數學習之__missing__

#對於get() 方法,它是有傳回值的,而且可以傳入第二個參數,作為key 不存在時的回傳內容,因此也可以接受。但是,另外兩種寫法都會報錯。

為了解決後兩種寫法的問題,就可以用到 __missing__() 魔術方法。

現在,假設我們有一個這樣的訴求:從字典中取某個key 對應的value,如果有值則回傳值,如果沒有值則插入key,並且給它一個預設值(例如一個空列表)。

如果用原生的dict,並不太好實現,但是,Python 提供了一個非常好用的擴展類別collections.defaultdict

Python魔法函數學習之__missing__

如圖所示,當取不存在的key 時,沒有再報KeyError,而是預設存入字典中。

為什麼 defaultdict 可以做到這一點呢?

原因是defaultdict 在繼承了內建類型dict 之後,也定義了一個__missing__() 方法,當__getitem__取不存在的值時,它就會呼叫入參中傳入的工廠函數(上例是呼叫list(),建立空列表)。

作為最典型的範例,defaultdict 在文件註解中寫到:

Python魔法函數學習之__missing__

#簡而言之,__missing__()的主要作用就是由__getitem__在缺少key 時調用,從而避免出現KeyError。

另一個典型的使用範例是collections.Counter,它也是dict 的子類,在取未被統計的key 時,回傳計數0:

Python魔法函數學習之__missing__

2、神出鬼沒的__missing__()

由上可知,__missing__()在__getitem__()取不到值時會被調用,但是,我不經意間也發現了一個細節:__getitem__()在取不到值時,並不一定會呼叫__missing__()。

這是因為它並非內建類型的必要屬性,並沒有在字典基底類別中被預先定義。

如果你直接從 dict 類型中取該屬性值,會報屬性不存在:AttributeError: type object 'object' has no attribute '__missing__'

使用dir() 查看,發現確實不存在該屬性:

Python魔法函數學習之__missing__

#如果從dict 的父類別即object 中查看,也會發現相同的結果。

這是怎麼回事?為什麼在 dict 和 object 中都沒有__missing__屬性呢?

然而,查閱最新的官方文檔,object 中分明包含這個屬性:

Python魔法函數學習之__missing__

#出處:https://docs.python.org/3/ reference/datamodel.html?highlight=__missing__#object.__missing__

#也就是說,理論上object 類別中會預先定義__missing__,其文檔證明了這一點,然而實際上它並沒有被定義!文檔與現實出現了偏差!

如此一來,當dict 的子類別(例如defaultdict 和Counter)在定義__missing__ 時,這個魔術方法事實上只屬於該子類,也就是說,它是一個誕生於子類別中的魔術方法!

據此,我有一個不成熟的猜想:__getitem__()會判斷當前物件是否是dict 的子類,且是否擁有__missing__(),然後才會去呼叫它(如果父類別中也有該方法,則不會先作判斷,而是直接就呼叫了)。

我在交流群組裡說出了這個猜想,有同學很快在 CPython 原始碼中找到驗證:

Python魔法函數學習之__missing__

而這就有意思了,在內建類型的子類別上才存在的魔術方法,縱觀整個 Python 世界,恐怕再難找出第二例。

我突然有一個聯想:這神出鬼沒的__missing__(),就像是一個擅長玩「大變活人」的魔術師,先讓觀眾在外面透過玻璃看到他(即官方文件),然而揭開門時,他並不在裡面(即內置類型),再變換一下道具,他又完好無損就出現了(即dict 的子類)。

3、被施魔法的__missing__()

__missing__() 的神奇之處,除了它本身會變成「魔術」之外,它還需要一股強大的「魔法」才能驅動。

在上篇文章中,我發現原生的魔術方法間相互獨立,它們在C 語言介面可能有相同的核心邏輯,但是在Python 語言介面,卻不存在呼叫關係:

Python魔法函數學習之__missing__

魔術方法的這種「老死不相往來」的表現,違背了一般的程式碼重複使用原則,也是導致內建類型的子類別會出現某些奇怪表現的原因。

官方 Python 寧肯提供新的 UserString、UserList、UserDict 子類,也不願意復用魔術方法,唯一合理的解釋似乎是令魔術方法相互呼叫的代價太大。

但是,對於特例__missing__(),Python 卻不得不妥協,不得不付出這種代價!

__missing__() 是魔術方法的“二等公民”,它沒有獨立的調用入口,只能被動地由__getitem__() 調用,即__missing__() 依賴於__getitem__()。

不同於那些“一等公民”,例如__init__()、__enter__()、__len__()、__eq__() 等等,它們要么是在物件生命週期或執行過程的某個節點被觸發,要麼由某個內建函數或操作符觸發,這些都是相對獨立的事件,無所依賴。

__missing__() 依賴__getitem__(),才能實作方法呼叫;而 __getitem__() 也要依賴 __missing__(),才能實現完整功能。

為了實現這一點,__getitem__()在解釋器程式碼中開了個後門,從 C 語言介面折回 Python 介面,去呼叫那個名為「__missing__」的特定方法。

Python魔法函數學習之__missing__

而這就是真正的「魔法」了,目前為止,__missing__()似乎是唯一一個享受了此等待遇的魔術方法!

4、小結

Python 的字典提供了兩種取值的內建方法,即__getitem__() 和get(),當取值不存在時,它們的處理策略是不一樣的:前者會報錯誤KeyError,而後者會回傳None。

為什麼 Python 要提供兩個不同的方法呢?或者應該問,為什麼 Python 要令這兩個方法做出不一樣的處理呢?

這可能有一個很複雜(也可能很簡單)的解釋,本文暫不深了。

不過有一點是可以確定的:即原生 dict 類型簡單粗暴地拋KeyError的做法有所不足。

為了讓字典類型有更強大的表現(或者說讓__getitem__()作出get() 那樣的表現),Python 讓字典的子類別可以定義__missing__(),供__getitem__()查找調用。

本文梳理了__missing__()的實現原理,從而揭示出它並非是一個毫不起眼的存在,恰恰相反,它是唯一一個打破了魔術方法間壁壘,支持被其它魔術方法調用的特例!

Python 為了維持魔術方法的獨立性,不惜煞費苦心地引入了 UserString、UserList、UserDict 這些派生類,但是對於 __missing__(),它卻選擇了妥協。

推薦學習:python教學

以上是Python魔法函數學習之__missing__的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn