深入理解 Python 內(nèi)部函數(shù)和閉包(進(jìn)階)
大家好,我是安果!
本文以?xún)?nèi)部函數(shù)為主線,深入講解內(nèi)部函數(shù)和閉包的應(yīng)用場(chǎng)景和原理,學(xué)會(huì)后你的 Python 水平會(huì)再上一個(gè)臺(tái)階,對(duì)工作面試或?qū)崙?zhàn)應(yīng)用都會(huì)很有幫助
本文包括:
函數(shù)是一等公民 內(nèi)部函數(shù)定義 閉包和 nonlocal 關(guān)鍵詞 應(yīng)用場(chǎng)景 - 封裝 應(yīng)用場(chǎng)景 - 函數(shù)生成器 函應(yīng)用場(chǎng)景 - 裝飾器 閉包實(shí)現(xiàn)原理
函數(shù)是一等公民
Python 是面向?qū)ο蟮木幊陶Z(yǔ)言,對(duì)象是 Python 的一等公民,我們常用的字符串str,整數(shù)?int,和其他變量都是對(duì)象
函數(shù)也是對(duì)象,所以也是一等公民,這就意味著它和變量一樣
可以作為參數(shù)被傳遞 可以在函數(shù)內(nèi)部定義 可以作為函數(shù)返回值 函數(shù)可以賦值給變量
def?say_hello():
????print('hello')
print(say_hello)
def?say_something(some_func):
????for?_?in?range(3):
????????some_func()
say_something(say_hello)
執(zhí)行結(jié)果:
<function?say_hello?at?0x7ff3d35b9160>
hello
hello
hello
內(nèi)部函數(shù)
把函數(shù)的內(nèi)部定義函數(shù),就是內(nèi)部函數(shù)(有點(diǎn)像廢話,但就那么個(gè)意思)
def?outter():
????print('我是外部函數(shù)')
????def?inner():
????????print('我是outter的內(nèi)部函數(shù)')
????print('調(diào)用內(nèi)部函數(shù)')
????inner()
????print('我再次調(diào)用內(nèi)部函數(shù),自己家的想用就用,隨時(shí)用')
????inner()
????print('還可以返回給大家共用')
????return?inner?
#調(diào)用外部函數(shù),并接受返回值
func?=?outter()
#調(diào)用outter返回的內(nèi)部函數(shù)
print('在外部調(diào)用內(nèi)部函數(shù)')
func()
注意: 調(diào)用的時(shí)候加小括號(hào)inner(),作為參數(shù)或者返回值的時(shí)候不加小括號(hào)inner,是引用這個(gè)函數(shù)對(duì)象
執(zhí)行結(jié)果:
我是外部函數(shù)
調(diào)用內(nèi)部函數(shù)
我是outter的內(nèi)部函數(shù)
我再次調(diào)用內(nèi)部函數(shù),自己家的想用就用,隨時(shí)用
我是outter的內(nèi)部函數(shù)
還可以返回給大家共用
在外部調(diào)用內(nèi)部函數(shù)
我是outter的內(nèi)部函數(shù)
閉包
如果內(nèi)部函數(shù)只是把函數(shù)定義在函數(shù)的內(nèi)部,那就沒(méi)有多大意思了,它還有一個(gè)很大的特點(diǎn),正因?yàn)檫@個(gè)特點(diǎn),它才被稱(chēng)為:閉包 clsure
學(xué)過(guò) JavaScript 的非小白同學(xué)可能會(huì)對(duì)這個(gè)概念很熟悉
內(nèi)部函數(shù)還有一個(gè)很重要的特性:
可以訪問(wèn)它所屬的外部函數(shù)的局部變量,這些變量被稱(chēng)為 nonlocal,或者enclosing 變量 可以攜帶這些 nonlocal 變量,讓它們不會(huì)被回收
所以說(shuō) Python 中的閉包就是內(nèi)部函數(shù),準(zhǔn)確點(diǎn)說(shuō),它是使用了 nonlocal 變量的內(nèi)部函數(shù)
import?random
def?create_room():
????room_no?=?random.randint(1,?100)?
????print(f'我創(chuàng)建了房間號(hào):{room_no}')
????def?toilet():
????????print(f'我是{room_no}的內(nèi)部廁所')
????print('上廁所')
????toilet()
????print('我再次上廁所,自己家的想用就用,隨時(shí)用')
????toilet()
????print('還可以共享給大家共用')
????return?toilet
#調(diào)用外部函數(shù),并接受返回值
toilet?=?create_room()
#調(diào)用outter返回的內(nèi)部函數(shù)
print('在外部使用內(nèi)部廁所')
toilet()
print('在外部再次使用內(nèi)部廁所')
toilet()
運(yùn)行結(jié)果:
我創(chuàng)建了房間號(hào):52
上廁所
我是52的內(nèi)部廁所
我再次上廁所,自己家的想用就用,隨時(shí)用
我是52的內(nèi)部廁所
還可以共享給大家共用
在外部使用內(nèi)部廁所
我是52的內(nèi)部廁所
在外部再次使用內(nèi)部廁所
我是52的內(nèi)部廁所
在調(diào)用一個(gè) create_room 的時(shí)候臨時(shí)生成了房間號(hào),一個(gè)局部變量 room_no 在內(nèi)部函數(shù) toilet 中可以直接訪問(wèn)外部函數(shù)的局部變量,這是內(nèi)部函數(shù)的特性。 局部變量 room_no 本來(lái)在函數(shù)執(zhí)行完就釋放的,但由于內(nèi)部函數(shù) toilet 引用了它就不會(huì)被釋放了,在外部調(diào)用的時(shí)候仍然可以引用到,如此就形成了閉包
說(shuō)的這么玄乎,其實(shí)就是內(nèi)部函數(shù)使用了外部函數(shù)的局部變量,所以局部變量被內(nèi)部函數(shù)給封存了,也就不會(huì)釋放了
nonlocal關(guān)鍵詞
內(nèi)部函數(shù)也可以改寫(xiě)外部函數(shù)的變量值,但需要使用 nonlocal 關(guān)鍵詞聲明這是外部的變量。
回憶一下:函數(shù)內(nèi)部修改全局變量,需要使用 global 關(guān)鍵詞
import?random
def?create_room():
????room_no?=?random.randint(1,?100)
????print(f'我創(chuàng)建了房間號(hào):{room_no}')
????def?toilet():
????????nonlocal?room_no
????????room_no?=?random.randint(1,?100)
????????print(f'我是{room_no}的內(nèi)部廁所')
????
????print('上廁所')
????toilet()
????print(f'房間號(hào):{room_no}')
????print('我再次上廁所,自己家的想用就用,隨時(shí)用')
????toilet()
????print(f'房間號(hào):{room_no}')
????print('還可以共享給大家共用')
????return?toilet
#調(diào)用外部函數(shù),并接受返回值
toilet?=?create_room()
#調(diào)用outter返回的內(nèi)部函數(shù)
print('在外部使用內(nèi)部廁所')
toilet()
print('在外部再次使用內(nèi)部廁所')
toilet()
使用 nonlocal 在內(nèi)部函數(shù)改變外部變量 room_no 的值,所以每次上廁所,都會(huì)改變房間號(hào):
我創(chuàng)建了房間號(hào):39
上廁所
我是43的內(nèi)部廁所
房間號(hào):43
我再次上廁所,自己家的想用就用,隨時(shí)用
我是66的內(nèi)部廁所
房間號(hào):66
還可以共享給大家共用
在外部使用內(nèi)部廁所
我是52的內(nèi)部廁所
在外部再次使用內(nèi)部廁所
我是29的內(nèi)部廁所
其實(shí)內(nèi)部函數(shù)就這么點(diǎn)東西了(后面再說(shuō)它的實(shí)現(xiàn)原理),現(xiàn)在來(lái)看到底有什么實(shí)實(shí)在在的用處。
下面來(lái)說(shuō) 3 個(gè)應(yīng)用場(chǎng)景:
應(yīng)用場(chǎng)景1 - 封裝
寫(xiě)在內(nèi)部是因?yàn)橹挥性趦?nèi)部才有用,外部根本不需要,也不想讓他們使用,就像上面的內(nèi)部廁所的例子,實(shí)際上是不可能在外面使用的,這種場(chǎng)景叫做 封裝
import?random
def?create_room():
????room_no?=?random.randint(1,?100)
????print(f'我創(chuàng)建了房間號(hào):{room_no}')
????def?toilet():
????????print(f'歡迎進(jìn)入{room_no}的VIP廁所')
????????print('沖水')
????????print('請(qǐng)君入廁')
????????print('洗手')
????????print('熱毛巾')
????????print('歡迎下次光臨')
????print('上廁所')
????toilet()
????print('上廁所')
????toilet()
????print('上廁所')
????toilet()
#調(diào)用外部函數(shù),并接受返回值
create_room()
上廁所是 create_room 所獨(dú)有的配方,不希望外面使用 上廁所的過(guò)程獨(dú)有配方是比較復(fù)雜的,有必要封裝到函數(shù)內(nèi),否則每次上廁所都要重復(fù)這些代碼
再總結(jié)一下:
封裝一方面不希望對(duì)外暴露函數(shù) 也為了方便內(nèi)部的重用
應(yīng)用場(chǎng)景2 - 函數(shù)生成器
內(nèi)部函數(shù)可以方便的生成新的函數(shù),看這個(gè)例子:
def?team_maker(type,?level,?temperature):
????'''
????type:?品種,如綠茶,紅茶
????level:?等級(jí),特級(jí),一級(jí),二級(jí)
????temper:?溫度
????'''
????def?tea(water):
????????print('正在沏茶中')
????????print(f'{type},{level},?{temperature}')
????????print(f'{water}毫升給您沖好了')
????return?tea
#創(chuàng)建符合我口味的沏茶函數(shù)
mytea?=?team_maker('綠茶',?'特級(jí)',?'66.6')
#創(chuàng)建符合她口味的沏茶函數(shù)
herteam?=?team_maker('紅茶',?'一級(jí)',?'88.8')
print('我們來(lái)一杯')
mytea(500)
herteam(300)
print('多喝點(diǎn)')
mytea(800)
herteam(600)
print('完了,我喝醉了...,因?yàn)橛兴?)
沏茶需要傳入多個(gè)參數(shù),有點(diǎn)麻煩,而每個(gè)人的口味相對(duì)比較固定 我們用內(nèi)部函數(shù)創(chuàng)建了一個(gè)符合我口味的沏茶函數(shù),以后調(diào)用這個(gè)函數(shù)就行了,我也給她創(chuàng)建了一個(gè)符合她口味的沏茶函數(shù)
運(yùn)行結(jié)果:
我們來(lái)一杯
正在沏茶中
綠茶,特級(jí),?66.6
500毫升給您沖好了
正在沏茶中
紅茶,一級(jí),?88.8
300毫升給您沖好了
多喝點(diǎn)
正在沏茶中
綠茶,特級(jí),?66.6
800毫升給您沖好了
正在沏茶中
紅茶,一級(jí),?88.8
600毫升給您沖好了
完了,我喝醉了...
應(yīng)用場(chǎng)景3 - 裝飾器
裝飾器,對(duì) Python 至關(guān)重要,它也是內(nèi)部函數(shù)的主要使用場(chǎng)景
閉包實(shí)現(xiàn)原理
閉包攜帶了外部函數(shù)的變量,所以可以訪問(wèn)這些變量,而這些變量也不會(huì)被釋放
具體是怎么實(shí)現(xiàn)的呢?答案就 1 個(gè)字:__closure__屬性
Python 給內(nèi)部函數(shù)添加了這個(gè)屬性來(lái)攜帶內(nèi)部函數(shù)用到的外部函數(shù)中的變量
import?random
def?create_room():
????room_no?=?random.randint(1,?100)
????print(f'我創(chuàng)建了房間號(hào):{room_no}')
????def?toilet():
????????print(f'我是{room_no}的內(nèi)部廁所')
????return?toilet
print('調(diào)用外部函數(shù)')
toilet?=?create_room()
print('打印一下toilet函數(shù)的變量,其中有一個(gè)是__closure__')
print(dir(toilet))
print('__closure__是一個(gè)包含它攜帶的變量的元組')
print(toilet.__closure__)
print('__closure__元組里是cell,通過(guò)cell_contents可以訪問(wèn)所攜帶的變量值')
print(toilet.__closure__[0].cell_contents)
執(zhí)行結(jié)果:
import?random
def?create_room():
????room_no?=?random.randint(1,?100)
????print(f'我創(chuàng)建了房間號(hào):{room_no}')
????def?toilet():
????????print(f'我是{room_no}的內(nèi)部廁所')
????return?toilet
print('調(diào)用外部函數(shù)')
toilet?=?create_room()
print('打印一下toilet函數(shù)的變量,其中有一個(gè)是__closure__')
print(dir(toilet))
print('__closure__是一個(gè)包含它攜帶的變量的元組')
print(toilet.__closure__)
print('__closure__元組里是cell,通過(guò)cell_contents可以訪問(wèn)所攜帶的變量值')
print(toilet.__closure__[0].cell_contents)
最后
你動(dòng)動(dòng)手,就是對(duì)我最大的鼓勵(lì),本文內(nèi)容干貨,首先建議收藏
如果對(duì)你有幫助,請(qǐng)?點(diǎn)贊,在看,轉(zhuǎn)發(fā)。謝謝!
??分享、點(diǎn)贊、在看,給個(gè)三連擊唄!
