前提準備
首先我們要明白在python中一切皆物件,數字、字串、元組、串列、字典、函式、方法、類、模塊等等都是物件,
因為函式也是一個物件,所以函式能夠像其他變數一樣被當作引數傳給其他的函式,同樣函式能也能夠作為另一個函式的結果回傳
map、sorted,filter 這幾個函式都可以接受一個函式作為引數,具體用法就不在此贅述了
建議看一下這個文章自由變數
函式閉包
閉包產生的原因:出現了函式嵌套,且內層函式使用了不是自己作用域的變數
一般情況下,如果一個函式結束,函式的內部所有東西都會釋放掉,還給記憶體,區域變數都會消失,但是閉包是一種特殊情況,如果外函式在結束的時候發現有自己的臨時變數將來會在內部函式中用到,就把這個臨時變數系結給了內部函式,然后自己再結束,
閉包簡而言之就是將資料和內部函式封裝到一個包(區域)中
函式的閉包就是一種函式嵌套的一種特殊情況
閉包的使用方式
在講解閉包之前我們先看一些例子,希望大家帶著疑問去學習,去理解后面的講解
- 方式一:將某些變數定義在函式內部,不與全域的變數發生沖突
def outer(): name = "fish" def inner(): # 子函式可以有很多個 print(name) return inner f1 = outer() f1() # fish
我們可以看到,雖然
name = "fish"是區域的變數,但是在外部呼叫f1()的時候仍然可以正常的使用
- 方式二:將外部資料封裝到函式內部(重點)
def outer(name): def inner(): print(name) return inner # 將內部的函式回傳出去 name = 'fish' f1 = outer(name) name = 'tom' f2 = outer(name) f1() # 輸出 fish f2() # 輸出 tom
錯誤理解:在呼叫函式f1,f2時因為內部沒有定義name變數,所以函式執行時會去全域變數中找,此時全域變數name=tom結果應該都會輸出 tom
但是實際輸出f1()輸出fish,f2()輸出tom
我們能夠發現在呼叫
f1(),f2()使用的變數并沒有隨著全域變數的變化而改變,仍然是創建f1(),f2()函式物件時的內容
閉包的理論講解
下面我們一步步的解釋這個原因,由表及里的解釋
每執行一遍fn = outer(x) 就會生成一個函式物件,每一個函式物件都有自己的記憶體空間,也會被存盤到這個記憶體空間,這個程序就叫閉包,這個變數叫做自由變數后面再使用變數的時候就會從這個記憶體空間里面取值
注:這個圖不是實際的記憶體結構圖,只是為了便于講解
現在是不是有個疑問,怎么就閉包了?自由變數到底存到了哪里?
下面我們就根據原始碼來探究這個問題
在回憶一下文章開頭說的那句話python中一切皆物件,函式也是物件,是物件就有屬性,我們現在就有理由懷疑自由變數被存到了函式物件的屬性中,
事實上自由變數就是存到了函式物件的屬性中,我先說明出來,后面會有代碼的驗證
當執行執行outer函式結束時,如果內部函式inner中如果將來會使用自由變數,那么內部函式inner的__closure__ 屬性就會保存需要的自由變數,該屬性記錄著自由變數的地址,當閉包被呼叫時,系統就會根據該地址找到對應的自由變數,完成整體的函式呼叫,又因為f1和f2不是同一個函式物件,所以函式物件的__closure__ 屬性保存的資料也不同,
閉包的代碼驗證
下面讓我們用代碼驗證
- 內部函式沒有使用自由變數,此時它的
__closure__屬性為None
def outer(): def inner(): print('hello') return inner f = outer() print(f.__closure__) # None
- 內部函式使用自由變數,此時它的
__closure__屬性是一個元組里面存盤自由變數
def outer(): name = 'fish' age = 112 def inner(): print(name,age) return inner f = outer() print(f.__closure__) # (<cell at 0x000001ED4A6BAFD0: int object at 0x000001ED30E85750>, <cell at 0x000001ED4A6BAFA0: str object at 0x000001ED30FF74F0>)
內部函式沒有使用自由變數:函式物件的__closure__屬性沒有值,
內部函式使用自由變數: 函式物件的__closure__屬性是一個元組,里面存著自由變數的地址,__closure__屬性可以存盤多個自由變數的內容
最終我們回到一開始的三個例子中,驗證__closure__屬性是不是閉包中資料存放的位置
# 依舊是實體中的函式 def outer(name): def inner(): print(name) return inner # 將內部的函式回傳出去 name = 'fish' f1 = outer(name) name = 'tom' f2 = outer(name) # 解釋一下代碼的含義 # 取出__closure__[0]中的第一個元素,回傳的是一個cell單元格物件 # .cell_contents 獲取cell單元格物件具體的值 print(f1.__closure__) #(<cell at 0x000001615108AA90: str object at 0x00000161379E4B70>,) print(f1.__closure__[0].cell_contents) # fish print(f2.__closure__[0].cell_contents) # tom f1() # 輸出 fish f2() # 輸出 tom
通過我們的實際操作以及輸出,我們發現__closure__屬性保存的內容就是創建函式物件時的自由變數
經過理論加代碼驗證相信你已經能夠理解Python的閉包的真正情況,我們可以很明顯的認識到原來閉包也沒有這么神秘,
最后我們總結一下函式的閉包
閉包就是嵌套函式的一種特殊情況,內層函式需要使用自由變數,這個變數不會隨著外層函式的結束而被銷毀,它會被保存到函式物件的__closure__屬性中,
閉包的實戰
- 裝飾器
Python中大名鼎鼎的裝飾器的實作就有閉包的思想,關于這個裝飾器,這里就不具體闡述,過兩天我會更新一個關于裝飾器的博客
- 多執行緒爬蟲
暫時看不懂也沒有關系
from concurrent.futures.thread import ThreadPoolExecutor import requests def download(url): header = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" } res = requests.get(url=url,headers=header) return res.text def outer(file_name): def write_file(response): # print(type(response.result())) content = response.result() print(file_name) with open(file_name,'w',encoding='utf8') as fp: fp.write(content) return write_file Pool = ThreadPoolExecutor(5) url_arr = [ ("https://www.163.com/news/article/HJQC8KGG000189FH.html",'1.txt'), ("https://www.163.com/news/article/HJQCBM89000189FH.html",'2.txt'), ("https://www.163.com/news/article/HJPTU9E8000189FH.html",'3.txt'), ("https://www.163.com/news/article/HJQ64VA1000189FH.html",'4.txt'), ("https://www.163.com/news/article/HJQ0V8VJ000189FH.html",'5.txt') ] for item in url_arr: future = Pool.submit(download,item[0]) # 通過submit提交執行的函式到執行緒池中 future.add_done_callback(outer(item[1])) Pool.shutdown()
總結
通過這篇文章希望大家能夠明白什么是Python的閉包,通過代碼驗證我們可以閉包并不神秘,只是函式物件的一個屬性保存了自由變數,Python的閉包獨自使用的情況很少,通常都是和其他的應用一塊使用,但是閉包這個思想很重要,面試經常問到,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/517519.html
標籤:Python
上一篇:__call__用法簡談
下一篇:編程思想
