在編程語言中有兩個很基礎的概念,即方法(method)和函數(function)。若是達到了編程初級/入門級水平,那麼你確定在心中已有了初步的答案。python
也許在你心中已有答案了編程
除去入參、返回值、匿名函數之類的正確的形式內容以外,你也許會說「函數就是定義在類外面的,而方法就是定義在類裏面的,跟類綁定的」。編程語言
這種說法有沒有問題呢?固然有!否則我就不會專門寫這篇文章了,本文主要會來釐清這個問題。函數
在標準庫inspect
中,它提供了兩個自省的函數,即 ismethod() 和 isfunction(),能夠用來判斷什麼是方法,什麼是函數。ui
所以,本文想要先來研究一下這兩個函數,看看 Python 在處理方法/函數的概念時,是怎麼作的?spa
關於它們的用法,先看一個最簡單的例子:3d
運行的結果分別是「True」和「False」,代表咱們所定義的 test() 是一個函數,而不是一個方法。code
這兩個函數也能夠用來檢測自身,不難驗證出它們都是一種函數:對象
那麼,接下來的問題是:inspect 庫的兩個函數是什麼工做原理呢?blog
先來看看 inspect 中的實現代碼:
在源碼中,咱們看到了 isinstance() 函數,它主要用於判斷一個對象(object)是不是某個類(class)的實例(instance)。
咱們還看到了 types.FunctionType
及types.MethodType
,它們指的就是目標類。繼續點進去看源碼:
# 摘自 types.py def _f(): pass FunctionType = type(_f) class _C: def _m(self): pass MethodType = type(_C()._m)
這裏只是定義了兩個空的 _f() 和 _m(),而後就使用了內置的 type() 函數。因此,咱們徹底能夠把它們摘出來,看看廬山真面目:
梳理它們的關係,能夠獲得:
通過簡化處理後,咱們發現最關鍵的是兩個問題:type() 函數如何判斷出一個對象是 function 或 method 類?instance() 函數如何判斷出一個對象是某個類的實例?
這兩個內置函數都是用 C 語言實現的,這裏我就不打算繼續深究了……
可是,讓咱們再回頭看看 inspect 中的註釋,就會注意到一些端倪:
仍是註釋更管用啊,由此咱們能獲得以下的推論:
一、非用戶定義的函數,即內置函數,在 isfunction() 眼裏並非「函數」(FunctionType)!
下面驗證一下 len()、dir() 和 range():
事實上,它們有專屬的類別(BuiltinFunctionType、BuiltinMethodType):
特別須要注意的是,內置函數都是builtin_function_or_method
類型,可是 range()、type()、list() 等看起來像是函數的,其實否則:
(PS:關於這點,我這篇文章 曾提到過,就再也不展開了。)
二、一個類的靜態方法,在 ismethod() 眼裏並非方法(MethodType)!
建立了類的實例後,再看看:
能夠看出,除了 classmethod 以外,只有類實例的實例方法,纔會被 ismethod() 斷定爲真!而靜態方法,無論綁定在類仍是實例上,都不算是「方法」!
有沒有以爲很難以想象(或者有點理不清了)?
好了,回到本文開頭的問題,咱們最後來小結一下吧。
若以 inspect 庫的兩個函數爲判斷依據,則 Python 中的「方法與函數」具備必定的狹義性。在判斷什麼是函數時,它們並不把內置函數計算在內。同時,在判斷什麼是方法時,並不是定義在類內部的都算,而是隻有類方法及綁定了實例的實例方法纔算是「方法」。
也許你會說,inspect 的兩個判斷函數並不足信,內置函數也應該算是「函數」,類裏面的全部方法都應該算是「方法」。
我認可這種說法在廣義上是可接受的,畢竟咱們一直叫的就是「XX函數」、「XX方法」嘛。
可是,理論和廣義概念只是方便人們的溝通理解,而代碼實現纔是本質的區別。也就是說,Python 在實際區別「方法與函數」時,並非文中開頭的簡單說法,還有更多的細節值得關注。
看完本文,你有什麼想法呢?歡迎一塊兒交流。