網(wǎng)站首頁 編程語言 正文
1.定義
在函數(shù)內(nèi)部再定義一個函數(shù),并且這個函數(shù)用到了外部函數(shù)的變量(LEGB),最后返回新建函數(shù)的函數(shù)名索引,那么將這樣的能夠訪問其定義時所在的作用域的函數(shù)以及用到的一些變量稱之為閉包。被引用的非全局變量也稱為自由變量?。這個自由變量保存在外部函數(shù)的只讀屬性?__closure__?中,會與內(nèi)層函數(shù)產(chǎn)生一個綁定關(guān)系,也就是自由變量將不會在內(nèi)存中輕易消失。如下例所示:
# 計算函數(shù)被調(diào)用的次數(shù) def counter(FIRST=0): -----------------__closure__--------------- |cnt = [FIRST] | # 之所以選列表是因為作用域問題,詳見后文 | | |def add_one(): | | cnt[0] += 1 | | return cnt[0] | ------------------------------------------ return add_one # 每當(dāng)外部函數(shù)被調(diào)用時,都將重新定義內(nèi)部的函數(shù),而變量 cnt 的值也可能不同 num5 = counter(5) num10 = counter(10) print(num5()) # 6 print(num5()) # 7 print(num10()) # 11 print(num10()) # 12 # 如果這個函數(shù)僅僅是嵌套函數(shù),那么它的 __closure__ 應(yīng)該是 None print(num5.__closure__) # (,) print(num5.__closure__[0].cell_contents) # 7 print(num10.__closure__[0].cell_contents) # 12 # 或者通過 __code__.co_freevars 查看函數(shù)中是否有自由變量,如果有自由變量,即為閉包 print(num10.__code__.co_freevars) # ('cnt',) |
2.nonlocal 關(guān)鍵字
上面代碼中的 cnt 變量是一個列表,可變對象,但如果是不可變對象,如:numer、tuple 等呢?
def counter(FIRST=0): cnt = FIRST # number def add_one(): cnt += 1 return cnt return add_one num5 = counter(5) print(num5.__closure__) print(num5.__code__.co_freevars) print(num5()) ---------------------------------------------------------------------------- def counter(FIRST=0): cnt = (FIRST,) # tuple def add_one(): cnt[0] += 1 return cnt[0] return add_one num5 = counter(5) print(num5.__closure__) print(num5.__code__.co_freevars) print(num5())
以上實例輸出結(jié)果:
None () Traceback (most recent call last): File "test.py", line, inprint(num5()) File "test.py", line, in add_one cnt += 1 UnboundLocalError: local variable 'cnt' referenced before assignment ---------------------------------------------------------------------------- ( ,) ('cnt',) Traceback (most recent call last): File "test.py", line, in | print(num5()) File "test.py", line, in add_one cnt[0] += 1 TypeError: 'tuple' object does not support item assignment
可以看出,此時 cnt 不再是自由變量,而是變成了局部變量,且提示 UnboundLocalError 未綁定局部錯誤。為什么不是自由變量了呢?為什么列表就沒問題呢?
這是因為?Python 中并沒有要求先聲明一個變量才能使用它,Python 解釋器認(rèn)為:在函數(shù)體內(nèi),只要對一個變量進(jìn)行賦值操作,那么這個變量就是局部變量。
Python的模塊代碼執(zhí)行之前,并不會經(jīng)過預(yù)編譯,模塊內(nèi)的函數(shù)體代碼在運行前會經(jīng)過預(yù)編譯,因此不管變量名的綁定發(fā)生在作用域的那個位置,都能被編譯器知道。
而 cnt += 1 相當(dāng)于 cnt = cnt + 1,對 cnt 進(jìn)行了賦值操作,所以 Python 解釋器認(rèn)為 cnt 是函數(shù)內(nèi)的局部變量,但是執(zhí)行的時候,先執(zhí)行 cnt+1 時發(fā)現(xiàn):
因為先前已經(jīng)認(rèn)定 cnt 為局部變量了,現(xiàn)在在局部作用域內(nèi)找不到 cnt 的值,也不會再到外部作用域找了,就會報錯。所以說現(xiàn)在 cnt 已經(jīng)不是自由變量了。
那么 tuple 類型的 cnt 呢?首先 cnt[0] = cnt[0] + 1,雖然有賦值,但是其左邊也是 cnt[0],cnt 是從外邊作用域索引了的。
所以,你看它顯示的結(jié)果:此時,cnt 確實也是自由變量的,但是它是不可變對象啊,所以報了 TypeError 錯誤。這下列表為什么行,你應(yīng)該知道了。
或者你使用?nonolocal?關(guān)鍵字,這個關(guān)鍵字的用法與 global 很像,讓你能夠給外部作用域(非全局作用域)內(nèi)的變量賦值。它可以使得一個被賦值的局部變量變?yōu)樽杂勺兞浚⑶?nonlocal聲明的變量發(fā)生變化時,__closure__中存儲的值也會發(fā)生變化:
def counter(FIRST=0): cnt = FIRST # number def add_one(): nonlocal cnt cnt += 1 return cnt return add_one num5 = counter(5) print(num5.__closure__) print(num5.__code__.co_freevars) print(num5())
(,) ('cnt',) 6 |
nonlocal 和 global
def scope_test(): spam = "test spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" do_nonlocal() print("After nonlocal assignment:", spam) # nonlocal spam do_global() print("After global assignment:", spam) # nonlocal spam scope_test() print("In global scope:", spam) # global spam
After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam
3.注意事項
lambda 自由參數(shù)之坑,特別是和列表解析或for循環(huán)結(jié)合使用時。lambda para_list : expression == > def?(para_list): return expression
#---CASE1 fs = [lambda j:i*j for i in range(3)] print([f(2) for f in fs]) #---CASE2 fs = map(lambda i:(lambda j: i*j), range(3)) print([f(2) for f in fs]) #---CASE3 fs = [(lambda i:lambda j:i*j)(i) for i in range(3)] print([f(2) for f in fs])
[4, 4, 4] [0, 2, 4] [0, 2, 4]
首先,CASE1 和 CASE3 顯然都是每循環(huán)一次,就添加一個 lambda 函數(shù)到列表中,不同的是,CASE1 添加的 lambda 函數(shù)中的 i 每次并沒有接收 for 循環(huán)中 i 的值,它只是定義的時候指了下 i,所以說,CASE1 中的幾個 lambda 函數(shù)的 i,是最后調(diào)用的時候,也就是 f(2) 時才到外層作用域找它的值的,此時找到的 i 的值就是里面 for 循環(huán)結(jié)束時的 i 的值。CASE3 則是一開始定義、添加的時候就給 i 賦好了初值。CASE2 則是因為 map 每次迭代的時候都會將一個可迭代對象的元素傳給了 i,所以 CASE2 里面的每個 lambda 函數(shù)的 i 也是各有各的值的。
像這種 lambda 的自由參數(shù)的問題的話,如果你不是故意這么做的話,還是轉(zhuǎn)為默認(rèn)參數(shù)的好:
fs = [lambda x: x+i for i in range(3)] print([f(2) for f in fs]) fs = [lambda x, i=i: x+i for i in range(3)] print([f(2) for f in fs])
[4, 4, 4] [2, 3, 4]
另外,就是列表解析里面的作用域是一個全新的作用域,和普通的 for 循環(huán)則有所不同:
#---CASE4 fs = [lambda j:i*j for i in range(3)] print([f(2) for f in fs]) i = 4 print([f(2) for f in fs]) #---CASE5 fs = [] for i in range(3): fs.append(lambda j:i*j) print([f(2) for f in fs]) i = 4 print([f(2) for f in fs])
[10, 10, 10] [10, 10, 10] [10, 10, 10] [8, 8, 8]
4.使用場景
裝飾器
惰性求值,比較常見的是在數(shù)據(jù)庫訪問的時候,可參考 Django 的 queryset 的實現(xiàn)
需要對某個函數(shù)的參數(shù)提前賦值的情況;當(dāng)然也可以使用 functools.parial 的偏函數(shù):functools.partial(func, *args, **kw),返回一個 partial 函數(shù)對象。
# y = a*x + b, a 和 b 可能只出現(xiàn)一次, x 會出現(xiàn)多次 def line(a, b, x): return a*x + b print(line(3, 4, 5)) print(line(3, 4, 6)) print(line(7, 4, 5)) print(line(7, 4, 6)) # 2.使用閉包 def line(a, b): def value(x): return a*x + b return value # y = 3x + 4 line1 = line(3, 4) print(line1(5)) print(line1(6)) print(line1(7)) # y = 9x + 7 line2 = line(9, 7) print(line2(5)) print(line2(6)) print(line2(7)) # 3.使用 functools.partial 偏函數(shù) from functools import partial line3 = partial(line, 3) print(line3) # functools.partial(, 3) print(line3(4, 5)) line4 = partial(line, 3, 4) print(line4(5)) print(line4(6)) print(line4(7)) line5 = partial(line, 9, 7) print(line5(5)) print(line5(6)) print(line5(7))
簡單總結(jié)functools.partial的作用就是:其能把一個函數(shù)的某些參數(shù)給固定住(也就是設(shè)置默認(rèn)值),并返回一個新的函數(shù),調(diào)用這個新函數(shù)會更簡單。
總結(jié)
原文鏈接:https://www.cnblogs.com/htzy/p/15965388.html
相關(guān)推薦
- 2022-10-09 ASP.NET泛型四之使用Lazy<T>實現(xiàn)延遲加載_實用技巧
- 2023-02-09 Python關(guān)鍵字?asynico基本用法_python
- 2022-06-04 Python?os和os.path模塊詳情_python
- 2022-08-15 前端寫代碼的時候,不滿足條件程序停止執(zhí)行下面的程序,并彈窗提示
- 2022-11-19 詳解C語言內(nèi)核中的自旋鎖結(jié)構(gòu)_C 語言
- 2022-11-19 ElasticSearch事件查詢語言EQL操作_服務(wù)器其它
- 2022-08-01 C++簡單又輕松的講解類和對象中友元函數(shù)_C 語言
- 2022-06-21 C++分析講解類的靜態(tài)成員變量是什么_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支