Python閉包概念入門
'''
Python閉包概念入門
閉包(Closure)是 Python 中一個重要的工具。
閉包:高階函數中,內層函數攜帶外層函數中的參數、變量及其環境,一同存在的狀態(即使已經離開了創造它的外層函數),被稱之為閉包。
被攜帶的外層變量稱之為:自由變量,也被形容為:外層變量被閉包捕獲了。
閉包中的自由變量有兩個神奇的特性:
1. 第一個特性是,自由變量在閉包存在的期間,其中的值也會一直存在。因此閉包可以持有狀態。
2. 另一個特性是,閉包與閉包之間的狀態是隔離的。
閉包的幾個特性:
它是一個嵌套函數
它可以訪問外部作用域中的自由變量
它從外層函數返回
'''
# 在 Python 中,函數可以根據給定的參數返回一個值:
def hello(name):
return "hello " + name
print(hello("Bob"))
# 與 Python 的其他對象(如字符串、整數、列表等)一樣,函數也是對象,也可以賦值給一個變量
h = hello
print(hello) # 輸出: <function hello at 0x108e903a0>
print(h) # 輸出: <function hello at 0x108e903a0>
# 可以看到 hello 和 h 都指向同一個函數,而函數后加括號 h('Jack') 是對其進行了調用。
print(h('jack')) # 輸出: hello jack
''' 1.函數里的函數 '''
def hi():
def bob():
return 'Bob'
print('Hi ' + bob())
# 輸出: Hi Bob
hi()
# 此時的 bob 函數的作用域在 hi 之內的。如果在全局調用 bob() 會引發錯誤:
# bob()
'''
2.函數作為返回值
函數可以作為返回值,也可以內部定義。這種在函數里傳遞、嵌套、返回其他函數的情況,稱之為高階函數。
函數還可以作為其他函數的參數。
閉包:高階函數中,內層函數攜帶外層函數中的參數、變量及其環境,一同存在的狀態(即使已經離開了創造它的外層函數),被稱之為閉包。
被攜帶的外層變量稱之為:自由變量,也被形容為:外層變量被閉包捕獲了。
'''
def cook():
def tomato():
print('I am Tomato')
return tomato
t = cook()
t() # 輸出: I am Tomato
'''
3. 閉包與自由變量:
通常來說,函數中的變量為局部變量,一但函數執行完畢,其中的變量就不可用了:
'''
def cook():
food = 'apple'
cook()
# print(food) # 輸出報錯:NameError: name 'food' is not defined
'''
同樣的情況到了高階函數這里,就有點不對勁了:
cook() 函數執行之后,按道理來說 food 變量就應該被銷毀掉了。但實際上沒有任何報錯, value() 順利的輸出了 food 的值。
甚至于,即使將 cook() 函數銷毀了,food 的值都還存在:
'''
def cook():
food = 'apple'
def wrapper():
print(food)
return wrapper
value = cook()
value() # 輸出:apple
# 刪除原函數
del cook
value() # 輸出:apple
'''
4. 參數捕獲:
很多時候,我們希望閉包所捕獲的自由變量可以根據不同的情況有所區分。
很簡單,把它作為外層函數的參數就可以了:
'''
def cook(name):
def wrapper():
print('I am cooking ' + name)
return wrapper
apple = cook('apple')
pear = cook('pear')
# 外層函數的參數也可以成為自由變量,被封裝到內層函數所在的環境中
# 這種局部變量起作用的特定環境,有時候被稱為作用域或者域。
apple() # 輸出: I am cooking apple
pear() # 輸出: I am cooking pear
'''
5. 函數生成:
既然外層函數可以攜帶參數,那被返回的內層函數當然也可以帶參數:
'''
def outer(x):
def inner(y):
print(x + y)
return inner
# 看到兩個括號就代表進行了兩次函數調用。第一個括號對應 outer 的參數 x ,第二個括號里對應 inner 的參數 y。
outer(1)(2) # 輸出: 3
# 利用閉包攜帶參數并返回函數的這個特性,可以很方便的在一個底層的函數框架上,組裝出不同的功能
# 外層函數傳遞的參數甚至可以是個函數。
def add(x):
def inner(y):
print(x + y)
return inner
add_one = add(1)
add_ten = add(10)
add_one(5) # 輸出: 6
add_ten(5) # 輸出: 15
'''
6. 狀態持有:
閉包中的自由變量有兩個神奇的特性:
第一個特性是,自由變量在閉包存在的期間,其中的值也會一直存在。因此閉包可以持有狀態。
另一個特性是,閉包與閉包之間的狀態是隔離的。
以上兩個特性,使得閉包像一個微型的類,因為狀態持有和數據隱藏是類的基本功能。
它兩在使用上的建議是:如果你的狀態比較簡單,那么可以用閉包來實現;相反則使用類。
'''
# 記錄每次取得的分數- score 閉包打印的列表記錄了每次調用的結果。
def make_score():
lis = []
def inner(x):
lis.append(x)
print(lis)
return inner
score = make_score()
score(60) # 輸出:[60]
score(77) # 輸出:[60, 77]
score(88) # 輸出:[60, 77, 88]
# 另一個特性是,閉包與閉包之間的狀態是隔離的。
def make_score():
lis = []
def inner(x):
lis.append(x)
print(lis)
return inner
first = make_score()
second = make_score()
first(1) # 輸出:[1]
first(2) # 輸出:[1, 2]
second(3) # 輸出:[3]
second(4) # 輸出:[3, 4]
'''
7. 不變量狀態:
在上面的例子里,閉包用 lis.append() 直接操作了自由變量。
但如果要操作的自由變量是個不變量,比如數值型、字符串等,那么記得加 nonlocal 關鍵字:
此關鍵字就是在告訴解釋器:接下來的 total 不是本函數里的局部變量,你最好去閉包或是別的地方找找。
'''
# 記錄成績總分
def make_score():
total = 10
def inner(x):
nonlocal total
total += x
print(total)
return inner
total = make_score()
total(5) # 輸出:15
'''
8. 延遲陷阱:
閉包相關的常見陷阱
inner 是延遲執行的,直到真正調用前,都是沒進行內部操作的。
'''
funcs = []
for i in range(3):
def inner():
print(i)
funcs.append(inner)
# print(f'i is: {i}')
funcs[0]() # 輸出:2
funcs[1]() # 輸出:2
funcs[2]() # 輸出:2
# 解決方案就是用閉包將 i 的值立即捕獲:
funcs = []
for i in range(3):
def outer(a):
def inner():
print(a)
return inner
funcs.append(outer(i))
print(f'i is: {i}')
funcs[0]() # 輸出:0
funcs[1]() # 輸出:1
funcs[2]() # 輸出:2
"""
9. 組合函數:
用閉包實現函數的拼接
"""
def compose(g, f):
def inner(*args, **kwargs):
return g(f(*args, **kwargs))
return inner
# 被拼接函數1
def remove_lase(lis):
return lis[1:]
# 被拼接函數2
def remove_last(lis):
return lis[:-1]
# 進行函數合成
remove_last_remove_last = compose(remove_last, remove_lase)
new_list = remove_last_remove_last([1, 2, 3, 4, 5])
print(new_list) # 輸出:[2, 3, 4]
"""
10. 柯里化
柯里化是閉包的一種應用,它將一個多參數的函數轉換成一系列單參數函數。
"""
# 柯里化閉包函數
def curry(f):
argc = f.__code__.co_argcount
f_args = []
f_kwargs = {}
def g(*args, **kwargs):
nonlocal f_args, f_kwargs
f_args += args
f_kwargs.update(kwargs)
if len(f_args) + len(f_kwargs) == argc:
return f(*f_args, **f_kwargs)
else:
return g
return g
# 無關緊要的原函數
def add(a, b, c):
return a + b + c
# c_add 是被柯里化的新函數
c_add = curry(add)
本文來自博客園,作者:浪里小白龍qaq,轉載請注明原文鏈接:http://www.rzrgm.cn/xiao-bai-long/p/17877181.html

浙公網安備 33010602011771號