新聞中心
python中的閉包函數(shù)

員工經(jīng)過長期磨合與沉淀,具備了協(xié)作精神,得以通過團(tuán)隊的力量開發(fā)出優(yōu)質(zhì)的產(chǎn)品。創(chuàng)新互聯(lián)公司堅持“專注、創(chuàng)新、易用”的產(chǎn)品理念,因為“專注所以專業(yè)、創(chuàng)新互聯(lián)網(wǎng)站所以易用所以簡單”。公司專注于為企業(yè)提供成都網(wǎng)站建設(shè)、網(wǎng)站制作、微信公眾號開發(fā)、電商網(wǎng)站開發(fā),小程序開發(fā),軟件按需策劃等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。
通常我們定義函數(shù)都是這樣定義的:
def foo(): pass
其實在函數(shù)式編程中,函數(shù)里面還可以嵌套函數(shù),如下面這樣:
def foo():
print("hello world in foo")
def bar():
print("hello world in bar")此時我們調(diào)用foo函數(shù),執(zhí)行結(jié)果會是什么樣子的呢??
hello world in foo
結(jié)果如上所示,只會執(zhí)行foo函數(shù)的第一層函數(shù),bar函數(shù)是不會被執(zhí)行的。為什么呢?
實際上來說,不管函數(shù)寫在哪個部分,那都只是定義了一個函數(shù),只有這個函數(shù)被調(diào)用,函數(shù)內(nèi)部的語句才會被執(zhí)行。
在上面的例子中,bar函數(shù)雖然在foo函數(shù)內(nèi)部定義了,但是并沒有被執(zhí)行,所以bar函數(shù)是不會被執(zhí)行的。
這樣說來,定義在一個函數(shù)內(nèi)部的函數(shù)就沒什么作用了嗎??其實不是這樣的。
來看下面的例子,把bar函數(shù)作為一個值返回給foo函數(shù),來看執(zhí)行過程:
def foo():
print("hello world in foo")
def bar():
print("hello world in bar")
return bar
f1=foo()
print(f1)此時,由于bar函數(shù)作為一個返回值被返回給了foo,所以foo函數(shù)執(zhí)行結(jié)果是有返回值的,此時定義一個變量f1來接收foo函數(shù)的執(zhí)行返回結(jié)果,然后打印f1。
返回的結(jié)果如下:
hello world in foo.bar at 0x0000000002941A60>
可以看到首先打印了foo函數(shù)中定義的一個print語句,接著打印的是foo函數(shù)中包含的bar函數(shù)的內(nèi)存地址,既然是一個函數(shù)的內(nèi)存地址,當(dāng)然可以加括號來執(zhí)行這個函數(shù)。
def foo():
print("hello world in foo")
def bar():
print("hello world in bar")
return bar
f1=foo()
f1()此時,這段代碼的執(zhí)行結(jié)果為:
hello world in foo hello world in bar
兩個print語句都被打印出來了。
在上面的例子里,首先定義了一個函數(shù)foo,接著在foo函數(shù)內(nèi)部又嵌套定義了一個函數(shù)bar,然后返回函數(shù)bar的函數(shù)名,這就是閉包函數(shù)的定義方式。其實,閉包的定義就是一個函數(shù)內(nèi)部又嵌套了一個函數(shù)。
來看下面的這段代碼:
def foo():
print("hello world in foo")
name="python"
def bar():
print(name)
print("hello world in bar")
return bar
f1=foo()
f1()在上面的例子里,在外層函數(shù)中定義了一個變量name,然后在內(nèi)層函數(shù)中打印這個變量name。
此時執(zhí)行上面的代碼,在打印name這個變量的時候,會先在bar函數(shù)內(nèi)部查找name這個變量,但是bar函數(shù)里面是沒有name這個變量的,此時根據(jù)python查找變量的LEGB法則,會到bar函數(shù)的外面一層去繼續(xù)查找name這個變量,此時可以找到name這個變量,所以這里打印的foo函數(shù)中定義的name的值。
執(zhí)行上面的代碼,打印結(jié)果如下:
hello world in foo python hello world in bar
這里要記住很重要的一點就是:內(nèi)層函數(shù)引用了外層函數(shù)的局部變量。
來分析下上面的例子中程序的執(zhí)行過程:
首先運行foo函數(shù),foo函數(shù)的執(zhí)行結(jié)果是返回bar的函數(shù)名,此時又把foo函數(shù)的執(zhí)行結(jié)果定義給了變量f1,
所以此時f1就等于bar這個函數(shù)的內(nèi)存地址,然后f1加括號運行就表示運行了bar函數(shù)。
在執(zhí)行bar函數(shù)的過程中,bar函數(shù)訪問到了外層foo函數(shù)中定義的變量,這就是一個典型的閉包函數(shù)。
那使用閉包函數(shù)有什么好處呢??在上面的例子里,f1的值是bar函數(shù)的內(nèi)存地址,f1加括號運行就是在運行bar函數(shù)。
又由于f1是一個全局變量,這意味著可以在整個程序的任意位置都可以運行f1函數(shù),此時再定義一個函數(shù),在這個函數(shù)內(nèi)部調(diào)用f1函數(shù),
def foo():
print("hello world in foo")
name = "python"
def bar():
print(name)
print("hello world in bar")
return bar
f1 = foo()
def func():
name = "aaaaa"
f1()
func()來分析一下程序的執(zhí)行過程:
1.運行func函數(shù),程序會先在內(nèi)存中申請一塊空間以保存name變量的值,然后運行f1函數(shù),f1是在全局中定義的變量,所以一定可以找到f1函數(shù)的內(nèi)存地址。
2.f1加括號運行,就是在執(zhí)行一個閉包函數(shù),這個閉包函數(shù)內(nèi)部引用了name這個變量。
3.name這個變量在bar函數(shù)的外部已經(jīng)定義了,所以在func函數(shù)內(nèi)部調(diào)用f1函數(shù),也就是bar函數(shù)時,其引用的變量依然是foo函數(shù)內(nèi)部定義的name變量,而不是func函數(shù)內(nèi)部定義的name變量。
4.因為f1函數(shù)的內(nèi)部已經(jīng)包含了name這個函數(shù)的值,所以就算在func函數(shù)內(nèi)部也定義了name這個變量,程序執(zhí)行的結(jié)果打印的依然是foo函數(shù)內(nèi)部定義的name的值。
相關(guān)推薦:《Python視頻教程》
程序執(zhí)行結(jié)果:
hello world in foo python hello world in bar
怎樣驗證一個函數(shù)是閉包函數(shù)?首先,閉包函數(shù)都有一個特有的屬性:closure。
在上面的例子里,打印f1的__closure__屬性。
def foo():
name = "python"
def bar():
print(name)
print("hello world in bar")
return bar
f1 = foo()
print(f1.__closure__)打印結(jié)果如下:
(| ,) |
可以看到__closure__屬性的打印結(jié)果是一個元組形式的,其值就是f1函數(shù)的外層函數(shù)作用域,此時可以調(diào)用__closure__返回的元組的元素的cell_contents方法打印出name變量的值。
def foo():
name = "python"
def bar():
print(name)
print("hello world in bar")
return bar
f1 = foo()
print(f1.__closure__[0].cell_contents)打印結(jié)果如下:
python
可以看到程序已經(jīng)打印出name變量的值了,即然__closure__的返回結(jié)果是一個元組,那么這個元組中一定是可以包含多個值的,看下面的例子。
在foo函數(shù)內(nèi)部定義多個變量,然后在bar函數(shù)內(nèi)部打印幾個變量的值,然后運行這個閉包函數(shù),打印閉包函數(shù)的__closure__方法。
def foo():
print("hello world in foo")
name1 = "python1"
name2 = "python2"
name3 = "python3"
name4 = "python4"
def bar():
print(name1)
print(name2)
print(name3)
print(name4)
print("hello world in bar")
return bar
f1 = foo()
print(f1.__closure__)程序執(zhí)行結(jié)果:
(| , | , | , | ) |
由于在foo函數(shù)內(nèi)部定義了4個變量,而且在bar函數(shù)內(nèi)部引用了這4個變量,所以打印這個閉包函數(shù)的__closure__方法,返回的元組中就有4個元素。
現(xiàn)在可以分別打印返回的元組中的這4個字符串對象的值了。
def foo():
name1 = "python1"
name2 = "python2"
name3 = "python3"
name4 = "python4"
def bar():
print(name1)
print(name2)
print(name3)
print(name4)
print("hello world in bar")
return bar
f1 = foo()
print(f1.__closure__[0].cell_contents)
print(f1.__closure__[1].cell_contents)
print(f1.__closure__[2].cell_contents)
print(f1.__closure__[3].cell_contents)程序執(zhí)行結(jié)果:
python1 python2 python3 python4
那么現(xiàn)在還剩下最后一個問題了,那就是閉包函數(shù)的內(nèi)層函數(shù)一定要返回嗎??
來看下面一個例子:
def foo(): name = "python1" def bar(): print(name) print(bar.__closure__) foo()
定義了一個嵌套函數(shù),然后這個嵌套函數(shù)的內(nèi)層函數(shù)沒有被返回,而是直接打印內(nèi)層函數(shù)的__closure__方法,然后直接調(diào)用外層函數(shù)。
程序執(zhí)行結(jié)果:
(| ,) |
依然打印出了內(nèi)層函數(shù)的引用的變量對象,這說明閉包函數(shù)的內(nèi)層函數(shù)還一定要返回。
閉包函數(shù)的內(nèi)層函數(shù)可以調(diào)用全局變量嗎??
把外層函數(shù)內(nèi)部定義的變量改為全局變量,然后在內(nèi)層函數(shù)中引用這個變量。
name = "python1" def foo(): def bar(): print(name) print(bar.__closure__) f=foo() print(f)
程序執(zhí)行結(jié)果:
None None
可以看到,程序的執(zhí)行結(jié)果是兩個None,嵌套函數(shù)的內(nèi)層函數(shù)的__closure__函數(shù)的值為None,這說明foo函數(shù)的內(nèi)層嵌套函數(shù)bar調(diào)用的全局變量沒有成功,所以上面的例子不是一個閉包函數(shù)。
關(guān)于閉包函數(shù)的一些總結(jié):
閉包的定義為:
(1)在函數(shù)內(nèi)部定義的函數(shù),稱為內(nèi)部函數(shù)。
(2)內(nèi)部函數(shù)調(diào)用了外部函數(shù)的局部變量。
(3)即使內(nèi)部函數(shù)返回了,還是可以使用局部變量。
(4)通常閉包函數(shù)的內(nèi)層函數(shù)都要被返回給外部函數(shù)。
(5)閉包函數(shù)的外部函數(shù)可以在任何地方被調(diào)用,而不再受函數(shù)定義時層級的限制。
閉包函數(shù)的作用
1.閉包函數(shù)自帶函數(shù)作用域
正常意義上的函數(shù),在函數(shù)執(zhí)行過程中查找變量的順序是一層一層向外找,符合LEGB(Local->Enclose->Global->Built in)法則的,
但是對閉包函數(shù)來說,查找變量只會找內(nèi)部函數(shù)外面的那一層,因為閉包函數(shù)本身就自帶一層作用域,這樣才符合"閉包"兩個字的意思。
2.延遲計算(也叫惰性計算)
看下面的例子
def func(): name="python" def bar(): print(name) return bar f=func() print(f.__closure)
在上面的例子里,執(zhí)行foo()函數(shù)的返回結(jié)果是一個包含自帶的某種狀態(tài)的函數(shù),實際上這個函數(shù)并沒有執(zhí)行,
以后想執(zhí)行這個自帶狀態(tài)的函數(shù)時,把func()返回結(jié)果所賦值的那個變量加括號就可以執(zhí)行了。
3.要想讓一個函數(shù)始終保持一種狀態(tài),就可以使用閉包。
例子:
name="python"
def func():
print("I like %s" % name)
func()上面的代碼執(zhí)行結(jié)果會打印一行:"I like python"
但是我們知道,在不同的地方調(diào)用func函數(shù),打印的結(jié)果很大可能是不一樣的,那么如果我想不管在什么地方調(diào)用func函數(shù),打印的結(jié)果都是"I like python"時,就可以使用閉包了。
def func1():
name="python"
def func():
print("I like %s" % name)
return func
func=func1()
func()如上圖所示,在func函數(shù)外面再包含一層函數(shù)func1,執(zhí)行func1函數(shù),再把func1函數(shù)的返回結(jié)果賦值給func這個變量。
此時func就是一個閉包函數(shù)了,把func函數(shù)加括號就可以執(zhí)行了。
而且我們一定知道,此時func函數(shù)的執(zhí)行結(jié)果一定會打印"I like python"這句話,而且不管func函數(shù)在程序的哪個位置被調(diào)用,執(zhí)行結(jié)果都是一樣的。
網(wǎng)頁名稱:創(chuàng)新互聯(lián)Python教程:Python之初識閉包函數(shù)
當(dāng)前網(wǎng)址:http://www.dlmjj.cn/article/djhhiso.html


咨詢
建站咨詢
