在編程語言中有兩個很基礎(chǔ)的概念,即方法(method)和函數(shù)(function)恬口。如果達到了編程初級/入門級水平校读,那么你肯定在心中已有了初步的答案。
也許在你心中已有答案了
除去入?yún)⒆婺堋⒎祷刂登革⒛涿瘮?shù)之類的正確的形式內(nèi)容之外,你也許會說“函數(shù)就是定義在類外面的养铸,而方法就是定義在類里面的雁芙,跟類綁定的”。
這種說法有沒有問題呢钞螟?當(dāng)然有兔甘!不然我就不會專門寫這篇文章了,本文主要會來厘清這個問題筛圆。
在標(biāo)準(zhǔn)庫inspect
中裂明,它提供了兩個自省的函數(shù),即 ismethod() 和 isfunction()太援,可以用來判斷什么是方法闽晦,什么是函數(shù)。
因此提岔,本文想要先來研究一下這兩個函數(shù)仙蛉,看看 Python 在處理方法/函數(shù)的概念時,是怎么做的碱蒙?
關(guān)于它們的用法荠瘪,先看一個最簡單的例子:
運行的結(jié)果分別是“True”和“False”,表明我們所定義的 test() 是一個函數(shù)赛惩,而不是一個方法哀墓。
這兩個函數(shù)也可以用來檢測自身,不難驗證出它們都是一種函數(shù):
那么喷兼,接下來的問題是:inspect 庫的兩個函數(shù)是什么工作原理呢篮绰?
先來看看 inspect 中的實現(xiàn)代碼:
在源碼中,我們看到了 isinstance() 函數(shù)季惯,它主要用于判斷一個對象(object)是否是某個類(class)的實例(instance)吠各。
我們還看到了 types.FunctionType
及types.MethodType
臀突,它們指的就是目標(biāo)類。繼續(xù)點進去看源碼:
# 摘自 types.py
def _f(): pass
FunctionType = type(_f)
class _C:
def _m(self): pass
MethodType = type(_C()._m)
這里只是定義了兩個空的 _f() 和 _m()贾漏,然后就使用了內(nèi)置的 type() 函數(shù)候学。所以,我們完全可以把它們摘出來纵散,看看廬山真面目:
梳理它們的關(guān)系梳码,可以得到:
經(jīng)過簡化處理后,我們發(fā)現(xiàn)最關(guān)鍵的是兩個問題:type() 函數(shù)如何判斷出一個對象是 function 或 method 類困食?instance() 函數(shù)如何判斷出一個對象是某個類的實例边翁?
這兩個內(nèi)置函數(shù)都是用 C 語言實現(xiàn)的,這里我就不打算繼續(xù)深究了……
但是硕盹,讓我們再回頭看看 inspect 中的注釋符匾,就會注意到一些端倪:
- isfunction() 判斷出的是用戶定義的函數(shù)(user-defined function), 它擁有__doc__瘩例、__name__ 等等屬性
- ismethod() 判斷出的是實例方法(instance method)啊胶, 它擁有函數(shù)的一些屬性,最特別的是還有一個 __self__ 屬性
還是注釋更管用啊垛贤,由此我們能得到如下的推論:
1焰坪、非用戶定義的函數(shù),即內(nèi)置函數(shù)聘惦,在 isfunction() 眼里并不是“函數(shù)”(FunctionType)某饰!
下面驗證一下 len()、dir() 和 range():
事實上善绎,它們有專屬的類別(BuiltinFunctionType黔漂、BuiltinMethodType):
特別需要注意的是,內(nèi)置函數(shù)都是builtin_function_or_method
類型禀酱,但是 range()炬守、type()、list() 等看起來像是函數(shù)的剂跟,其實不然:
(PS:關(guān)于這點减途,我這篇文章 曾提到過,就不再展開了曹洽。)
2鳍置、一個類的靜態(tài)方法,在 ismethod() 眼里并不是方法(MethodType)送淆!
創(chuàng)建了類的實例后墓捻,再看看:
可以看出,除了 classmethod 之外,只有類實例的實例方法砖第,才會被 ismethod() 判定為真!而靜態(tài)方法环凿,不管綁定在類還是實例上梧兼,都不算是“方法”!
有沒有覺得很不可思議(或者有點理不清了)智听?
好了羽杰,回到本文開頭的問題,我們最后來小結(jié)一下吧到推。
若以 inspect 庫的兩個函數(shù)為判斷依據(jù)考赛,則 Python 中的“方法與函數(shù)”具有一定的狹義性。在判斷什么是函數(shù)時莉测,它們并不把內(nèi)置函數(shù)計算在內(nèi)颜骤。同時,在判斷什么是方法時捣卤,并非定義在類內(nèi)部的都算忍抽,而是只有類方法及綁定了實例的實例方法才算是“方法”。
也許你會說董朝,inspect 的兩個判斷函數(shù)并不足信鸠项,內(nèi)置函數(shù)也應(yīng)該算是“函數(shù)”,類里面的所有方法都應(yīng)該算是“方法”子姜。
我承認這種說法在廣義上是可接受的祟绊,畢竟我們一直叫的就是“XX函數(shù)”、“XX方法”嘛哥捕。
但是牧抽,理論和廣義概念只是方便人們的溝通理解,而代碼實現(xiàn)才是本質(zhì)的區(qū)別扭弧。也就是說阎姥,Python 在實際區(qū)別“方法與函數(shù)”時,并不是文中開頭的簡單說法鸽捻,還有更多的細節(jié)值得關(guān)注呼巴。
看完本文,你有什么想法呢御蒲?歡迎一起交流衣赶。