0. 函數(shù)相關(guān)知識
1)Python中“一切皆對象”,函數(shù)也不例外
先定義一個函數(shù):
def func():
? ? print('我是函數(shù):{}'.format(func.__name__))? # __name__屬性返回函數(shù)的名稱
打印函數(shù)名:
>>> print(func)
<function func at 0x00000209EA722E18>
返回的是函數(shù)對象func,并指出了它的內(nèi)存地址眼溶。
對函數(shù)使用type():
>>> type(func)
<class 'function'>
以上可以看出 函數(shù) func 是 function類型的一個對象,屬于可調(diào)用對象(具有__call__魔法方法)梅桩。
2)函數(shù)可以賦值給一個變量
函數(shù)作為一個對象撕阎,它可以作為其他函數(shù)的參數(shù)准浴,也可以作為函數(shù)的返回值,還能夠賦值給一個變量:
>>> f = func? ? ?
>>> f()? ? ? ? ? # 可調(diào)用對象后加()即可執(zhí)行調(diào)用
我是函數(shù):func? ? ? # func賦值給變量f斤寇,f可以作為函數(shù)被調(diào)用桶癣,但函數(shù)名仍為func
將函數(shù)賦值給變量,是將函數(shù)的引用傳遞給變量娘锁。至此牙寞,函數(shù)名和變量共同指向 function對象func。
3)函數(shù)作為函數(shù)的返回值
定義一個可變參數(shù)的求和函數(shù):
def sum(*args):
? ? result = 0
? ? for n in args:
? ? ? ? result += n
? ? return result
如果此時調(diào)用函數(shù),將會立即求和间雀。
再定義一個函數(shù)悔详,它能夠延遲求和:
def lazy_sum(*args):
? ? def sum():
? ? ? result = 0
? ? ? for n in args:
? ? ? ? ? result += n
? ? ? return result
? ? return sum? ? ? ? ? ? ? ? # 函數(shù)sum作為返回值
調(diào)用函數(shù)lazy_sum()時,返回的是求和函數(shù)sum惹挟,而不是求和結(jié)果:
>>> f = lazy_sum(1, 2, 3)
>>> f
# f = 函數(shù)lazy_sum局部空間中的sum函數(shù)
<function lazy_sum.<locals>.sum at 0x000001ECB6C19840>
當(dāng)調(diào)用函數(shù)f時茄螃,才會真正計算求和的結(jié)果:
>>> f()
6
這種嵌套定義函數(shù),具有兩個特征:
1)內(nèi)部定義的函數(shù)使用了外部函數(shù)的參數(shù)或局部變量连锯;
2)外部函數(shù)的返回值為內(nèi)部函數(shù)(注意不是返回內(nèi)部函數(shù)的調(diào)用)归苍;
最后有一點需要注意,每次調(diào)用lazy_sum()运怖,都會返回一個新的函數(shù)拼弃,即使傳入相同的參數(shù):
>>> f1 = lazy_sum(1, 2, 3)
>>> f1 = lazy_sum(1, 2, 3)
>>> f1 == f2
False? ? # f1和f2指向的是不同內(nèi)存地址的sum函數(shù)
1.閉包
1)概念:
函數(shù)嵌套定義的前提下,如果內(nèi)部函數(shù)使用了外部函數(shù)的變量驳规,并且外部函數(shù)返回值為內(nèi)部函數(shù)肴敛,此時我們就把這個內(nèi)部函數(shù)稱為“閉包”署海。
2)特征:
也即閉包的構(gòu)成條件為:
(1)函數(shù)嵌套定義為前提吗购;
(2)內(nèi)部函數(shù)使用了外部函數(shù)的變量;
(3)外部函數(shù)的返回值為內(nèi)部函數(shù)砸狞;
3)原理:
在上面的lazy_sum()函數(shù)中:
def lazy_sum(*args):
? ? def sum():
? ? ? result = 0
? ? ? for n in args:
? ? ? ? ? result += n
? ? ? return result
? ? return sum? ? ? ? ? ? ? ? # 函數(shù)sum作為返回值
lazy_sum是外部函數(shù)捻勉,sum是內(nèi)部函數(shù),此時的sum就是閉包刀森。
閉包(內(nèi)部函數(shù))sum引用了外部函數(shù)lazy_sum的變量踱启,當(dāng)lazy_sum返回sum時,其相關(guān)參數(shù)和變量就保存在了閉包sum中研底。其中的過程:定義外部函數(shù) --> 操作系統(tǒng)為外部函數(shù)分配局部空間 --> 局部空間中定義變量和內(nèi)部函數(shù) --> 為內(nèi)部函數(shù)開辟嵌套的局部空間 --> 返回內(nèi)部函數(shù)埠偿。
過程中存在嵌套的局部空間(Enclosing),對于這個嵌套的局部空間來說榜晦,外部函數(shù)所在的局部空間就相當(dāng)于其全局空間冠蒋,其中的變量也相當(dāng)于全局變量,因此內(nèi)部函數(shù)可以像調(diào)用全局變量一樣調(diào)用外部函數(shù)的變量乾胶。而也是由于這個嵌套的局部空間抖剿,使得內(nèi)部函數(shù)保存了外部函數(shù)的變量。
閉包原理復(fù)雜识窿,應(yīng)用的時候牢記三點就可以:
1)閉包(內(nèi)部函數(shù))使用了外部函數(shù)的變量斩郎,并且能夠保存外部函數(shù)的相關(guān)變量;
2)外部函數(shù)返回的是閉包函數(shù)喻频,不是一個計算結(jié)果缩宜,該函數(shù)不會立即執(zhí)行;
3)調(diào)用外部函數(shù)并賦值給變量甥温,則該變量就等于保存了外部函數(shù)相關(guān)變量的內(nèi)部函數(shù)锻煌;
4)應(yīng)用:兩個小伙伴聊天
# 定義閉包
def person(a):
? ? def inner(b):
? ? ? print('{}:{}'.format(a, b))
? ? return inner
david = person('David')? ? # 建立一個閉包對象david
jane = person('Jane')? ? ? # 建立閉包對象jane
david('天王蓋地虎')
jane('寶塔鎮(zhèn)河妖')
>>> 運行結(jié)果:
David:天王蓋地虎
Jane:寶塔鎮(zhèn)河妖
可以看到兩種現(xiàn)象:1)調(diào)用person()時膜宋,inner延遲執(zhí)行;2)inner保存了person的變量a炼幔;
5)關(guān)鍵字nonlocal
關(guān)鍵字nonlocal能夠?qū)崿F(xiàn)對閉包外部函數(shù)變量的修改操作秋茫。nonlocal --> 聲明內(nèi)部函數(shù)的變量是非本地的。類似于定義普通函數(shù)時的聲明全局變量的關(guān)鍵字global乃秀。
# 定義閉包:不使用nonlocal
def outer():
? ? a = 10
? ? def inner():
? ? ? ? a = 20
? ? ? ? result = a + 1
? ? ? ? print(result)
? ? inner()
? ? print(a)?
? ? return inner
test1 = outer()? # 調(diào)用外層函數(shù)
test1()? ? ? # 調(diào)用內(nèi)層函數(shù)
>>> 運行結(jié)果:
21
10
21? ?
#? a的值仍為10肛著,說明內(nèi)部函數(shù)沒有修改掉外部函數(shù)變量a的值
#? 此種情況下內(nèi)部函數(shù)并沒有使用外部函數(shù)的變量,而是自建了一個新的變量a
# 定義閉包:使用nonlocal
def outer():
? ? a = 10
? ? def inner():
? ? ? ? nonlocal a
? ? ? ? a = 20
? ? ? ? result = a + 1
? ? ? ? print(result)
? ? inner()
? ? print(a)
? ? return inner
test1 = outer()? # 調(diào)用外層函數(shù)
test1()? ? ? # 調(diào)用內(nèi)層函數(shù)
>>> 運行結(jié)果
21
20
21? ?
# a的值被修改為20?
2.總結(jié)
閉包是Python中函數(shù)式編程的重要語法結(jié)構(gòu)跺讯,屬于函數(shù)的高階應(yīng)用枢贿。通過它的原理也能夠?qū)崿F(xiàn)“裝飾器”的定義。
3.Enclosing 補充
當(dāng)定義閉包函數(shù)時刀脏,外部函數(shù)的局部命名空間中會存在“Enclosing作用域”局荚。閉包函數(shù)會存在于這個Enclosing作用域中,可以通過閉包函數(shù)的__closure__屬性調(diào)出愈污。Enclosing除了存放閉包函數(shù)外耀态,還會自動添加閉包函數(shù)用到的外部函數(shù)的對象,但僅限自己所用的暂雹。
上面閉包應(yīng)用的例子:
# 定義閉包
def person(a):
? ? a1 = 10
? ? def inner(b):
? ? ? ? print(inner.__closure__)? ? ? # 打印閉包函數(shù)的__closure__屬性
? ? ? ? print('{}:{}'.format(a, b))
? ? return inner
inner.__closure__ = (<cell at 0x000001DC0A6D7E88: str object at 0x000001DC0A768E30>, <cell at 0x000001DC0A6D76A8: function object at 0x000001DC0A799620>)
str object 即是inner用到的參數(shù)a首装;function? object為它自己;而變量a1由于inner不會使用杭跪,所以不在其中仙逻。