以前寫過一篇關於裝飾器的文章,雖然寫得還算不錯,可是也有很多同窗表示沒看懂,我大概分析了其中的緣由,主要問題是他們不理解函數,由於Python中的函數不一樣於其它語言。javascript
正確理解 Python函數,可以幫助咱們更好地理解 Python 裝飾器、匿名函數(lambda)、函數式編程等高階技術。html
函數(Function)做爲程序語言中不可或缺的一部分,太稀鬆日常了。但函數做爲第一類對象(First-Class Object)倒是 Python 函數的一大特性。那到底什麼是第一類對象呢?java
在 Python 中萬物皆爲對象,函數也不例外,函數做爲對象能夠賦值給一個變量、能夠做爲元素添加到集合對象中、可做爲參數值傳遞給其它函數,還能夠當作函數的返回值,這些特性就是第一類對象所特有的。python
先來看一個簡單的例子編程
>>> def foo(text):
... return len(text)
...
>>> foo("zen of python")
13複製代碼
這是一個再簡單不過的函數,用於計算參數 text 的長度,調用函數就是函數名後面跟一個括號,再附帶一個參數,返回值是一個整數。函數式編程
函數身爲一個對象,擁有對象模型的三個通用屬性:id、類型、和值。函數
>>> id(foo)
4361313816
>>> type(foo)
<class 'function'>
>>> foo
<function foo at 0x103f45e18>複製代碼
做爲對象,函數能夠賦值給一個變量ui
>>> bar = foo複製代碼
賦值給另一個變量時,函數並不會被調用,僅僅是在函數對象上綁定一個新的名字而已。spa
>>> bar("zen of python")
13
>>>複製代碼
同理,你還能夠把該函數賦值給更多的變量,惟一變化的是該函數對象的引用計數不斷地增長,本質上這些變量最終指向的都是同一個函數對象。.net
>>> a = foo
>>> b = foo
>>> c = bar
>>> a is b is c
True複製代碼
容器對象(list、dict、set等)中能夠存聽任何對象,包括整數、字符串,函數也能夠做存放到容器對象中,例如
>>> funcs = [foo, str, len]
>>> funcs
[<function foo at 0x103f45e18>, <class 'str'>, <built-in function len>]
>>> for f in funcs:
... print(f("hello"))
...
5
hello
5
>>>複製代碼
foo 是咱們自定義的函數,str 和 len 是兩個內置函數。for 循環逐個地迭代出列表中的每一個元素時,函數對象賦值給了 f 變量,調用 f(「hello」) 與 調用 foo(「hello」) 本質是同樣的效果,每次 f 都從新指向一個新的函數對象。固然,你也能夠使用列表的索引定位到元素來調用函數。
>>> funcs[0]("Python之禪")
# 等效於 foo("Python之禪")
8複製代碼
函數還能夠做爲參數值傳遞給另一個函數,例如:
>>> def show(func):
... size = func("python 之禪") # 等效於 foo("Python之禪")
... print ("length of string is : %s" % size)
...
>>> show(foo)
length of string is : 9複製代碼
函數做爲另一個函數的返回值,例如:
>>> def nick():
... return foo
>>> nick
<function nick at 0x106b549d8> >>> a = nick() >>> a <function foo at 0x10692ae18> >>> a("python") 6複製代碼
還能夠簡寫爲
>>> nick()("python")
6複製代碼
函數接受一個或多個函數做爲輸入或者函數輸出(返回)的值是函數時,咱們稱這樣的函數爲高階函數,好比上面的 show 和 nick 都屬於高階函數。
Python內置函數中,典型的高階函數是 map 函數,map 接受一個函數和一個迭代對象做爲參數,調用 map 時,依次迭代把迭代對象的元素做爲參數調用該函數。
>>> map(foo, ["the","zen","of","python"])
>>> lens = map(foo, ["the","zen","of","python"])
>>> list(lens)
[3, 3, 2, 6]複製代碼
map 函數的做用至關於:
>>> [foo(i) for i in ["the","zen","of","python"]]
[3, 3, 2, 6]複製代碼
只不過 map 的運行效率更快一點。
Python還容許函數中定義函數,這種函數叫嵌套函數。
>>> def get_length(text):
... def clean(t): # 2
... return t[1:]
... new_text = clean(text) # 1
... return len(new_text)
...
>>> get_length("python")
5
>>>複製代碼
這個函數的目的是去除字符串的第一個字符後再計算它的長度,儘管函數自己的意義不大,但能足夠說明嵌套函數。get_length 調用時,先執行1處代碼,發現有調用 clean 函數,因而接着執行2中的代碼,把返回值賦值給了 new_text ,再繼續執行後續代碼。
>>> clean("python")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'clean' is not defined複製代碼
函數中裏面嵌套的函數不能在函數外面訪問,只能是在函數內部使用,超出了外部函數的作用域就無效了。
對於一個自定義的類,若是實現了 __call__ 方法,那麼該類的實例對象的行爲就是一個函數,是一個能夠被調用(callable)的對象。例如:
class Add:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
>>> add = Add(1)
>>> add(4)
>>> 5複製代碼
執行 add(4) 至關於調用 Add._call__(add, 4),self 就是實例對象 add,self.n 等於 1,因此返回值爲 1+4
add(4)
||
Add(1)(4)
||
Add.__call__(add, 4)複製代碼
肯定對象是否爲可調用對象能夠用內置函數callable來判斷。
>>> callable(foo)
True
>>> callable(1)
False
>>> callable(int)
True複製代碼
Python中包含函數在內的一切皆爲對象,函數做爲第一類對象,支持賦值給變量,做爲參數傳遞給其它函數,做爲其它函數的返回值,支持函數的嵌套,實現了__call__方法的類實例對象也能夠當作函數被調用。
這兩天我在公衆號 Python之禪 (id:VTtalk)發起了一次送圖書的福利活動,您能夠去看看
同步發表博客:foofish.net/function-is…