天天看點

詳解高階函數和閉包 | 手把手教你入門Python之四十五

上一篇: 遞歸函數和匿名函數的使用介紹 | 手把手教你入門Python之四十四 下一篇: 5個案例詳解裝飾器 | 手把手教你入門Python之四十六 本文來自于千鋒教育在阿裡雲開發者社群學習中心上線課程 《Python入門2020最新大課》 ,主講人姜偉。

高階函數

在Python中,函數其實也是⼀種資料類型。

def test():
 return 'hello world'
print(type(test)) # <class 'function'>           

函數對應的資料類型是 function ,可以把它當做是⼀種複雜的資料類型。

既然同樣都是⼀種資料類型,我們就可以把它當做數字或者字元串來處理。

定義⼀個變量指向函數

在Python中,我們還可以定義⼀個變量,讓它來指向⼀個函數,相當于給函數起了⼀個别名。

def test():
 return 'hello wrold'
fun = test # 定義了⼀個變量fun,讓它指向了 test 這個函數
print(fun()) # 使⽤fun()可以直接調⽤test這個函數
print(id(fun)) # 1819677672040
print(id(test)) # 1819677672040           

注意:在定義⼀個變量表示⼀個函數時,函數後⾯不能加括号!加括号表示的是調⽤這個函數。

def test():
 return 'hello world'
result = test() # 這種寫法是調⽤test函數,并把函數的傳回值指派給result變量
print(result()) # 這⾥會報錯 TypeError: 'str' object is not callable
fun = test # 這種寫法是給test函數起了⼀個别名,注意,這⾥的test後⾯不能加()
fun() # 可以使⽤别名調⽤這個函數           

⾼階函數

既然變量可以指向函數,函數的參數能接收變量,那麼⼀個函數就可以接收另⼀個函數作為參數,同樣,我們還可以把⼀個函數當做另⼀個函數的傳回值。這種函數的使⽤⽅式我們稱之為⾼階函數。

函數做為另⼀個函數的參數

def test(age,action):
 if age < 18:
 print('您還沒滿⼗⼋歲,請退出')
 action() # 把參數action直接當做⼀個函數來調⽤
def smoke():
 print('我已經年滿⼗⼋歲了,我想抽煙')

my_action = smoke # 定義⼀個變量my_action,讓它指向smoke函數
test(21, my_action) # 将my_action傳給 test 函數作為它的參數
test(21,smoke) # 還可以不再定義⼀個新的變量,直接傳⼊函數名           

函數作為另⼀個函數的傳回值

def test():
 print('我是test函數⾥輸⼊的内容')
def demo():
 print('我是demo⾥輸⼊的内容')
 return test # test 函數作為demo函數的傳回值
result = demo() # 我是demo⾥輸⼊的内容 調⽤ demo 函數,把demo函數的傳回值指派給 result
print(type(result)) # <class 'function'> result 的類型是⼀個函數
result() # 我是demo⾥輸⼊的内容 我是test函數⾥輸⼊的内容 既然result是⼀個函數,那麼就可
以直接使⽤() 調⽤這個函數
demo()() # 我是demo⾥輸⼊的内容 我是test函數⾥輸⼊的内容           
詳解高階函數和閉包 | 手把手教你入門Python之四十五
詳解高階函數和閉包 | 手把手教你入門Python之四十五
詳解高階函數和閉包 | 手把手教你入門Python之四十五
詳解高階函數和閉包 | 手把手教你入門Python之四十五

閉包

函數隻是⼀段可執⾏代碼,編譯後就“固化”了,每個函數在記憶體中隻有⼀份執行個體,得到函數的⼊⼝點便可以執⾏函數了。函數還可以嵌套定義,即在⼀個函數内部可以定義另⼀個函數,有了嵌套函數這種結構,便會産⽣閉包問題。

函數嵌套

在函數⾥⾯還可以定義函數,可以嵌套多層,執⾏需要被調⽤。

def outer():
 print('outer----hello')
 def inner(): # inner這個函數是在outer函數内部定義的
 print('inner----hello')
 inner() # inner函數隻在outer函數内部可⻅
outer()
# inner() 這⾥會報錯,在outer函數外部⽆法通路到inner函數           

什麼是閉包

閉包是由函數及其相關的引⽤環境組合⽽成的實體(即:閉包=函數塊+引⽤環境)。

def outer(n):
 num = n
 def inner():
 return num+1
 return inner
print(outer(3)()) # 4
print(outer(5)()) # 5           

在這段程式中,函數 inner 是函數 outer 的内嵌函數,并且 inner 函數是outer函數的傳回值。我們注意到⼀個問題:内嵌函數 inner 中引⽤到外層函數中的局部變量num,Python解釋器會這麼處理這個問題呢? 先讓我們來看看這段代碼的運⾏結果,當我們調⽤分别由不同的參數調⽤ outer 函數得到的函數時,得到的結果是隔離的(互相不影響),也就是說每次調⽤outer函數後都将⽣成并儲存⼀個新的局部變量num,這⾥outer函數傳回的就是閉包。 如果在⼀個内部函數⾥,對在外部作⽤域(但不是在全局作⽤域)的變量進⾏引⽤,那麼内部函數就被認為是閉包(closure)。

修改外部變量的值

閉包⾥預設不能修改外部變量。

def outer(n):
 num = n
 def inner():
 num = num + 1
 return num
 return inner
print(outer(1)())           

上述代碼運⾏時會報錯!

UnboundLocalError: local variable 'num' referenced before assignment           

原因分析

在python⾥,隻要看到了指派語句,就會認為指派語句的左邊是⼀個局部變量。 num = num + 1 這段代碼⾥, num 在 = 的左邊,python解析器會認為我們要修改 inner 函數⾥ num 這個局部變量,⽽這個變量使⽤之前是未聲明的,是以會報錯。

解決方案

我們分析過,報錯的原因在于當我們在閉包内修改外部變量時,會被python解析器誤會為内部函數的局部變量。是以,解決⽅案就在于,我們需要想辦法,讓解析器知道我們不是要修改局部變量,⽽是要修改外部變量。

  • 解決⽅法:使⽤ nonlocal 關鍵字
def outer(n):
 num = n
 def inner():
 nonlocal num # 修改前使⽤nonlocal關鍵字對 num 變量進⾏說明
 num = num + 1
 return num
 return inner
print(outer(2)())           
詳解高階函數和閉包 | 手把手教你入門Python之四十五

計算代碼的執行時長

詳解高階函數和閉包 | 手把手教你入門Python之四十五
詳解高階函數和閉包 | 手把手教你入門Python之四十五