
在編程語言中有兩個(gè)很基礎(chǔ)的概念,即方法(method)和函數(shù)(function)。如果達(dá)到了編程初級(jí)/入門級(jí)水平,那么你肯定在心中已有了初步的答案。
也許在你心中已有答案了
除去入?yún)?、返回值、匿名函?shù)之類的正確的形式內(nèi)容之外,你也許會(huì)說“函數(shù)就是定義在類外面的,而方法就是定義在類里面的,跟類綁定的”。
這種說法有沒有問題呢?當(dāng)然有!不然我就不會(huì)專門寫這篇文章了,本文主要會(huì)來厘清這個(gè)問題。
在標(biāo)準(zhǔn)庫inspect 中,它提供了兩個(gè)自省的函數(shù),即 ismethod() 和 isfunction(),可以用來判斷什么是方法,什么是函數(shù)。
因此,本文想要先來研究一下這兩個(gè)函數(shù),看看 Python 在處理方法/函數(shù)的概念時(shí),是怎么做的?
關(guān)于它們的用法,先看一個(gè)最簡(jiǎn)單的例子:

運(yùn)行的結(jié)果分別是“True”和“False”,表明我們所定義的 test() 是一個(gè)函數(shù),而不是一個(gè)方法。
這兩個(gè)函數(shù)也可以用來檢測(cè)自身,不難驗(yàn)證出它們都是一種函數(shù):

那么,接下來的問題是:inspect 庫的兩個(gè)函數(shù)是什么工作原理呢?
先來看看 inspect 中的實(shí)現(xiàn)代碼:


在源碼中,我們看到了 isinstance() 函數(shù),它主要用于判斷一個(gè)對(duì)象(object)是否是某個(gè)類(class)的實(shí)例(instance)。
我們還看到了 types.FunctionType 及types.MethodType ,它們指的就是目標(biāo)類。繼續(xù)點(diǎn)進(jìn)去看源碼:
# 摘自 types.py
def _f(): pass
FunctionType = type(_f)
class _C:
def _m(self): pass
MethodType = type(_C()._m)
這里只是定義了兩個(gè)空的 _f() 和 _m(),然后就使用了內(nèi)置的 type() 函數(shù)。所以,我們完全可以把它們摘出來,看看廬山真面目:

梳理它們的關(guān)系,可以得到:

經(jīng)過簡(jiǎn)化處理后,我們發(fā)現(xiàn)最關(guān)鍵的是兩個(gè)問題:type() 函數(shù)如何判斷出一個(gè)對(duì)象是 function 或 method 類?instance() 函數(shù)如何判斷出一個(gè)對(duì)象是某個(gè)類的實(shí)例?
這兩個(gè)內(nèi)置函數(shù)都是用 C 語言實(shí)現(xiàn)的,這里我就不打算繼續(xù)深究了……
但是,讓我們?cè)倩仡^看看 inspect 中的注釋,就會(huì)注意到一些端倪:
- isfunction() 判斷出的是用戶定義的函數(shù)(user-defined function), 它擁有__doc__、__name__ 等等屬性
- ismethod() 判斷出的是實(shí)例方法(instance method), 它擁有函數(shù)的一些屬性,最特別的是還有一個(gè) __self__ 屬性
還是注釋更管用啊,由此我們能得到如下的推論:
1、非用戶定義的函數(shù),即內(nèi)置函數(shù),在 isfunction() 眼里并不是“函數(shù)”(FunctionType)!
下面驗(yàn)證一下 len()、dir() 和 range():

事實(shí)上,它們有專屬的類別(BuiltinFunctionType、BuiltinMethodType):


特別需要注意的是,內(nèi)置函數(shù)都是builtin_function_or_method 類型,但是 range()、type()、list() 等看起來像是函數(shù)的,其實(shí)不然:

(PS:關(guān)于這點(diǎn),我這篇文章 曾提到過,就不再展開了。)
2、一個(gè)類的靜態(tài)方法,在 ismethod() 眼里并不是方法(MethodType)!

創(chuàng)建了類的實(shí)例后,再看看:

可以看出,除了 classmethod 之外,只有類實(shí)例的實(shí)例方法,才會(huì)被 ismethod() 判定為真!而靜態(tài)方法,不管綁定在類還是實(shí)例上,都不算是“方法”!
有沒有覺得很不可思議(或者有點(diǎn)理不清了)?
好了,回到本文開頭的問題,我們最后來小結(jié)一下吧。
若以 inspect 庫的兩個(gè)函數(shù)為判斷依據(jù),則 Python 中的“方法與函數(shù)”具有一定的狹義性。在判斷什么是函數(shù)時(shí),它們并不把內(nèi)置函數(shù)計(jì)算在內(nèi)。同時(shí),在判斷什么是方法時(shí),并非定義在類內(nèi)部的都算,而是只有類方法及綁定了實(shí)例的實(shí)例方法才算是“方法”。
也許你會(huì)說,inspect 的兩個(gè)判斷函數(shù)并不足信,內(nèi)置函數(shù)也應(yīng)該算是“函數(shù)”,類里面的所有方法都應(yīng)該算是“方法”。
我承認(rèn)這種說法在廣義上是可接受的,畢竟我們一直叫的就是“XX函數(shù)”、“XX方法”嘛。
但是,理論和廣義概念只是方便人們的溝通理解,而代碼實(shí)現(xiàn)才是本質(zhì)的區(qū)別。也就是說,Python 在實(shí)際區(qū)別“方法與函數(shù)”時(shí),并不是文中開頭的簡(jiǎn)單說法,還有更多的細(xì)節(jié)值得關(guān)注。
看完本文,你有什么想法呢?歡迎一起交流。