中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

代做cad平面圖的網(wǎng)站海南百度競價推廣

代做cad平面圖的網(wǎng)站,海南百度競價推廣,站長推薦跳轉,湖南人力資源網(wǎng)官網(wǎng)閉包并不只是一個python中的概念,在函數(shù)式編程語言中應用較為廣泛。理解python中的閉包一方面是能夠正確的使用閉包,另一方面可以好好體會和思考閉包的設計思想。 1.概念介紹 首先看一下維基上對閉包的解釋: 在計算機科學中,閉包…

閉包并不只是一個python中的概念,在函數(shù)式編程語言中應用較為廣泛。理解python中的閉包一方面是能夠正確的使用閉包,另一方面可以好好體會和思考閉包的設計思想。

1.概念介紹
首先看一下維基上對閉包的解釋:

在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認為閉包是由函數(shù)和與其相關的引用環(huán)境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實例。

簡單來說就是一個函數(shù)定義中引用了函數(shù)外定義的變量,并且該函數(shù)可以在其定義環(huán)境外被執(zhí)行。這樣的一個函數(shù)我們稱之為閉包。實際上閉包可以看做一種更加廣義的函數(shù)概念。因為其已經(jīng)不再是傳統(tǒng)意義上定義的函數(shù)。

根據(jù)我們對編程語言中函數(shù)的理解,大概印象中的函數(shù)是這樣的:

程序被加載到內(nèi)存執(zhí)行時,函數(shù)定義的代碼被存放在代碼段中。函數(shù)被調(diào)用時,會在棧上創(chuàng)建其執(zhí)行環(huán)境,也就是初始化其中定義的變量和外部傳入的形參以便函數(shù)進行下一步的執(zhí)行操作。當函數(shù)執(zhí)行完成并返回函數(shù)結果后,函數(shù)棧幀便會被銷毀掉。函數(shù)中的臨時變量以及存儲的中間計算結果都不會保留。下次調(diào)用時唯一發(fā)生變化的就是函數(shù)傳入的形參可能會不一樣。函數(shù)棧幀會重新初始化函數(shù)的執(zhí)行環(huán)境。

C++中有static關鍵字,函數(shù)中的static關鍵字定義的變量獨立于函數(shù)之外,而且會保留函數(shù)中值的變化。函數(shù)中使用的全局變量也有類似的性質(zhì)。

但是閉包中引用的函數(shù)定義之外的變量是否可以這么理解呢?但是如果函數(shù)中引用的變量既不是全局的,也不是靜態(tài)的(python中沒有這個概念)。應該怎么正確的理解呢?

建議先參考一下我的另一篇博文(Python??UnboundLocalError和NameError錯誤根源解析?),了解一下變量可見性和綁定相關的概念非常有必要。

2.閉包初探
為了說明閉包中引用的變量的性質(zhì),可以看一下下面的這個例子:

def outer_func():2     loc_list = []3     def inner_func(name):4         loc_list.append(len(loc_list) + 1)5         print '%s loc_list = %s' %(name, loc_list)6     return inner_func7 8 clo_func_0 = outer_func()9 clo_func_0('clo_func_0')
10 clo_func_0('clo_func_0')
11 clo_func_0('clo_func_0')
12 clo_func_1 = outer_func()
13 clo_func_1('clo_func_1')
14 clo_func_0('clo_func_0')
15 clo_func_1('clo_func_1')

程序的運行結果:

clo_func_0 loc_list = [1]
clo_func_0 loc_list = [1, 2]
clo_func_0 loc_list = [1, 2, 3]
clo_func_1 loc_list = [1]
clo_func_0 loc_list = [1, 2, 3, 4]
clo_func_1 loc_list = [1, 2]

從上面這個簡單的例子應該對閉包有一個直觀的理解了。運行的結果也說明了閉包函數(shù)中引用的父函數(shù)中l(wèi)ocal variable既不具有C++中的全局變量的性質(zhì)也沒有static變量的行為。

在python中我們稱上面的這個loc_list為閉包函數(shù)inner_func的一個自由變量(free variable)。

If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.

在這個例子中我們至少可以對閉包中引用的自由變量有如下的認識:

閉包中的引用的自由變量只和具體的閉包有關聯(lián),閉包的每個實例引用的自由變量互不干擾。
一個閉包實例對其自由變量的修改會被傳遞到下一次該閉包實例的調(diào)用。
由于這個概念理解起來并不是那么的直觀,因此使用的時候很容易掉進陷阱。

3.閉包陷阱
下面先來看一個例子:

1 def my_func(*args):2     fs = []3     for i in xrange(3):4         def func():5             return i * i6         fs.append(func)7     return fs8 9 fs1, fs2, fs3 = my_func()
10 print fs1()
11 print fs2()
12 print fs3()

上面這段代碼可謂是典型的錯誤使用閉包的例子。程序的結果并不是我們想象的結果0,1,4。實際結果全部是4。

這個例子中,my_func返回的并不是一個閉包函數(shù),而是一個包含三個閉包函數(shù)的一個list。這個例子中比較特殊的地方就是返回的所有閉包函數(shù)均引用父函數(shù)中定義的同一個自由變量。

但這里的問題是為什么for循環(huán)中的變量變化會影響到所有的閉包函數(shù)?尤其是我們上面剛剛介紹的例子中明明說明了同一閉包的不同實例中引用的自由變量互相沒有影響的。而且這個觀點也絕對的正確。

那么問題到底出在哪里?應該怎樣正確的分析這個錯誤的根源。

其實問題的關鍵就在于在返回閉包列表fs之前for循環(huán)的變量的值已經(jīng)發(fā)生改變了,而且這個改變會影響到所有引用它的內(nèi)部定義的函數(shù)。因為在函數(shù)my_func返回前其內(nèi)部定義的函數(shù)并不是閉包函數(shù),只是一個內(nèi)部定義的函數(shù)。

當然這個內(nèi)部函數(shù)引用的父函數(shù)中定義的變量也不是自由變量,而只是當前block中的一個local variable。

1 def my_func(*args):
2     fs = []
3     j = 0
4     for i in xrange(3):
5         def func():
6             return j * j
7         fs.append(func)
8     j = 2
9     return fs

上面的這段代碼邏輯上與之前的例子是等價的。這里或許更好理解一點,因為在內(nèi)部定義的函數(shù)func實際執(zhí)行前,對局部變量j的任何改變均會影響到函數(shù)func的運行結果。

函數(shù)my_func一旦返回,那么內(nèi)部定義的函數(shù)func便是一個閉包,其中引用的變量j成為一個只和具體閉包相關的自由變量。后面會分析,這個自由變量存放在Cell對象中。

使用lambda表達式重寫這個例子:

1 def my_func(*args):
2     fs = []
3     for i in xrange(3):
4         func = lambda : i * i
5         fs.append(func)
6     return fs

經(jīng)過上面的分析,我們得出下面一個重要的經(jīng)驗:返回閉包中不要引用任何循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。

這條規(guī)則本質(zhì)上是在返回閉包前,閉包中引用的父函數(shù)中定義變量的值可能會發(fā)生不是我們期望的變化。

正確的寫法:

1 def my_func(*args):
2     fs = []
3     for i in xrange(3):
4         def func(_i = i):
5             return _i * _i
6         fs.append(func)
7     return fs或者:1 def my_func(*args):
2     fs = []
3     for i in xrange(3):
4         func = lambda _i = i : _i * _i
5         fs.append(func)
6     return fs

正確的做法便是將父函數(shù)的local variable賦值給函數(shù)的形參。函數(shù)定義時,對形參的不同賦值會保留在當前函數(shù)定義中,不會對其他函數(shù)有影響。

另外注意一點,如果返回的函數(shù)中沒有引用父函數(shù)中定義的local variable,那么返回的函數(shù)不是閉包函數(shù)。

4.閉包的應用
自由變元可以記錄閉包函數(shù)被調(diào)用的信息,以及閉包函數(shù)的一些計算結果中間值。而且被自由變量記錄的值,在下次調(diào)用閉包函數(shù)時依舊有效。

根據(jù)閉包函數(shù)中引用的自由變量的一些特性,閉包的應用場景還是比較廣泛的。后面會有文章介紹其應用場景之一——單例模式,限于篇幅,此處以裝飾器為例介紹一下閉包的應用。

如果我們想對一個函數(shù)或者類進行修改重定義,最簡單的方法就是直接修改其定義。但是這種做法的缺點也是顯而易見的:

可能看不到函數(shù)或者類的定義
會破壞原來的定義,導致原來對類的引用不兼容
如果多人想在原來的基礎上定制自己函數(shù),很容易沖突
?使用閉包可以相對簡單的解決上面的問題,下面看一個例子

1 def func_dec(func):2     def wrapper(*args):3         if len(args) == 2:4             func(*args)5         else:6             print 'Error! Arguments = %s'%list(args)7     return wrapper8 9 @func_dec
10 def add_sum(*args):
11     print sum(args)
12 
13 # add_sum = func_dec(add_sum)
14 args = range(1,3)
15 add_sum(*args)

對于上面的這個例子,并沒有破壞add_sum函數(shù)的定義,只不過是對其進行了一層簡單的封裝。如果看不到函數(shù)的定義,也可以對函數(shù)對象進行封裝,達到相同的效果(即上面注釋掉的13行),而且裝飾器是可以疊加使用的。

4.1 潛在的問題
但閉包的缺點也是很明顯的,那就是經(jīng)過裝飾器裝飾的函數(shù)或者類不再是原來的函數(shù)或者類了。這也是使用裝飾器改變函數(shù)或者類的行為與直接修改定義最根本的差別。

實際應用的時候一定要注意這一點,下面看一個使用裝飾器導致的一個很隱蔽的問題。

1 def counter(cls):2     obj_list = []3     def wrapper(*args, **kwargs):4         new_obj = cls(*args, **kwargs)5         obj_list.append(new_obj)6         print "class:%s'object number is %d" % (cls.__name__, len(obj_list))7         return new_obj8     return wrapper9 
10 @counter
11 class my_cls(object):
12     STATIC_MEM = 'This is a static member of my_cls'
13     def __init__(self, *args, **kwargs):
14         print self, args, kwargs
15         print my_cls.STATIC_MEM

這個例子中我們嘗試使用裝飾器來統(tǒng)計一個類創(chuàng)建的對象數(shù)量。當我們創(chuàng)建my_cls的對象時,會發(fā)現(xiàn)something is wrong!

Traceback (most recent call last):File "G:\Cnblogs\Alpha Panda\Main.py", line 360, in <module>my_cls(1,2, key = 'shijun')File "G:\Cnblogs\Alpha Panda\Main.py", line 347, in wrappernew_obj = cls(*args, **kwargs)File "G:\Cnblogs\Alpha Panda\Main.py", line 358, in __init__print my_cls.STATIC_MEM
AttributeError: 'function' object has no attribute 'STATIC_MEM'

如果對裝飾器不是特別的了解,可能會對這個錯誤感到詫異。經(jīng)過裝飾器修飾后,我們定義的類my_cls已經(jīng)成為一個函數(shù)。

my_cls.__name__ == 'wrapper' and type(my_cls) is types.FunctionType
?my_cls被裝飾器counter修飾,等價于 my_cls = counter(my_cls)。

顯然在上面的例子中,my_cls.STATIC_MEM是錯誤的,正確的用法是self.STATIC_MEM。

對象中找不到屬性的話,會到類空間中尋找,因此被裝飾器修飾的類的靜態(tài)屬性是可以通過其對象進行訪問的。雖然my_cls已經(jīng)不是類,但是其調(diào)用返回的值卻是被裝飾之前的類的對象。

該問題同樣適用于staticmethod。那么有沒有方法得到原來的類呢?當然可以,my_cls().__class__便是被裝飾之前的類的定義。

那有沒有什么方法能讓我們還能通過my_cls來訪問類的靜態(tài)屬性,答案是肯定的。

1 def counter(cls):
2     obj_list = []
3     @functools.wraps(cls)
4     def wrapper(*args, **kwargs):
5         ... ...
6     return wrapper

改寫裝飾器counter的定義,主要是對wrapper使用functools進行了一次包裹更新,使經(jīng)過裝飾的my_cls看起來更像裝飾之前的類或者函數(shù)。該過程的主要原理就是將被裝飾類或者函數(shù)的部分屬性直接賦值到裝飾之后的對象。如WRAPPER_ASSIGNMENTS(__name__, __module__ and __doc__,?)和WRAPPER_UPDATES(__dict__)等。但是該過程不會改變wrapper是函數(shù)這樣一個事實。

my_cls.__name__ == 'my_cls' and type(my_cls) is types.FunctionType
5.閉包的實現(xiàn)
本著會用加理解的原則,可以從應用層的角度來稍微深入的理解一下閉包的實現(xiàn)。畢竟要先會用python么,如果一切都從源碼中學習,那成本的確有點高。

1 def outer_func():2     loc_var = "local variable"3     def inner_func():4         return loc_var5     return inner_func6 7 import dis8 dis.dis(outer_func)9 clo_func = outer_func()
10 print clo_func()
11 dis.dis(clo_func)

為了更加清楚理解上述過程,我們先嘗試給出outer_func.func_code中的部分屬性:

outer_func.func_code.co_consts:?(None, 'local variable', <code object inner_func at 025F7770, file "G:\Cnblogs\Alpha Panda\Main.py", line 207>)
outer_func.func_code.co_cellvars:('loc_var',)
outer_func.func_code.co_varnames:('inner_func',)
嘗試反匯編上面這個簡單清晰的閉包例子,得到下面的結果:

2            0 LOAD_CONST               1 ('local variable')   # 將outer_func.func_code.co_consts[1]放到棧頂3 STORE_DEREF              0 (loc_var)        # 將棧頂元素存放到cell對象的slot 0?3            6 LOAD_CLOSURE             0 (loc_var)        # 將outer_func.func_code.co_cellvars[0]對象的索引放到棧頂9 BUILD_TUPLE              1              # 將棧頂1個元素取出,創(chuàng)建元組并將元組壓入棧中12 LOAD_CONST              2 (<code object inner_func at 02597770, file "G:\Cnblogs\Alpha Panda\Main.py", line 207>) # 將outer_func.func_code.co_consts[2]放到棧頂15 MAKE_CLOSURE            0              # 創(chuàng)建閉包,此時棧頂是閉包函數(shù)代碼段的入口,棧頂下面則是函數(shù)的free variables,也就是本例中的'local variable ',將閉包壓入棧頂18 STORE_FAST              0 (inner_func)       # 將棧頂存放入outer_func.func_code.co_varnames[0]5            21 LOAD_FAST               0 (inner_func)       # 將outer_func.func_code.co_varnames[0]的引用放入棧頂24 RETURN_VALUE                       # Returns with TOS to the caller of the function.
local variable
4            0 LOAD_DEREF               0 (loc_var)         # 將cell對象中的slot 0對象的引用壓入棧頂3 RETURN_VALUE                          # Returns with TOS to the caller of the function

這個結果中,我們反匯編了外層函數(shù)及其返回的閉包函數(shù)(為了便于查看,修改了部分行號)。從對上面兩個函數(shù)的反匯編的注釋可以大致了解閉包實現(xiàn)的步驟。

python閉包中引用的自由變量實際存放在一個Cell對象中,當自由變元被閉包引用時,便將Cell中存放的自由變量的引用放入棧頂。

本例中Cell對象及其存放的自由變量分別為:

clo_func.func_closure[0] ? ?#Cell Object
clo_func.func_closure[0].cell_contents == 'local variable' ? ?# Free Variable


閉包實現(xiàn)的一個關鍵的地方是Cell Object,下面是官方給出的解釋:

“Cell” objects are used to implement variables referenced by multiple scopes. For each such variable, a cell object is created to store the value; the local variables of each stack frame that references the value contains a reference to the cells from outer scopes which also use that variable. When the value is accessed, the value contained in the cell is used instead of the cell object itself. This de-referencing of the cell object requires support from the generated byte-code; these are not automatically de-referenced when accessed. Cell objects are not likely to be useful elsewhere.

好了,限于篇幅就先介紹到這里。重要的是理解的基礎上靈活的應用解決實際的問題并避免陷阱,希望本文能讓你對閉包有一個不一樣的認識。

歡迎轉載博客文章,轉載請標明出處!

http://www.risenshineclean.com/news/9154.html

相關文章:

  • 嘉興做網(wǎng)站的公司有哪些廊坊百度關鍵詞排名平臺
  • 常州百度搜索優(yōu)化seo刷詞工具在線
  • java能做網(wǎng)站開發(fā)么超級推薦的關鍵詞怎么優(yōu)化
  • 網(wǎng)站規(guī)劃思想方法有哪些內(nèi)容電商運營工資大概多少
  • 58同城做公司網(wǎng)站怎修改做任務賺傭金的平臺
  • 成都網(wǎng)站建設哪家東莞seo建站公司
  • 周口師范做網(wǎng)站商丘seo排名
  • wordpress 后臺忘了如何做關鍵詞優(yōu)化
  • 什么網(wǎng)站可以做拍a發(fā)布會杭州seo網(wǎng)站
  • 實時爬蟲網(wǎng)站是怎么做的品牌網(wǎng)絡營銷策劃
  • 添加建設銀行的網(wǎng)站怎么查看域名是一級還是二級域名
  • 淄博網(wǎng)站建設報價seo營銷推廣多少錢
  • 公司網(wǎng)站設計欣賞關鍵詞優(yōu)化排名首頁
  • 一個網(wǎng)站做局打水山東最新資訊
  • 網(wǎng)站開發(fā)需要技術淘寶關鍵詞排名查詢工具免費
  • 濟南網(wǎng)站建設(力選聚搜網(wǎng)絡)搜索引擎排名影響因素有哪些
  • 怎么夸一個網(wǎng)站做的好看欽州seo
  • 管理系統(tǒng) 網(wǎng)站模板比較好的品牌策劃公司有哪些
  • 網(wǎng)頁設計圖片超鏈接海曙seo關鍵詞優(yōu)化方案
  • 平臺類網(wǎng)站有哪些搜索引擎分類
  • wordpress圖片css鄭州seo地址
  • 國展做網(wǎng)站的公司網(wǎng)絡推廣哪個好
  • 學做網(wǎng)站論壇vip賬戶如何注冊百度賬號
  • 北京工程建設交易網(wǎng)標題優(yōu)化
  • 建網(wǎng)站 免費搜索引擎營銷分析
  • 北京市保障性住房建設投資中心網(wǎng)站6搜索引擎下載安裝
  • ASP動態(tài)網(wǎng)站制作國外b站不收費免費2023
  • 大連市建委培訓官方網(wǎng)站微信公眾號營銷
  • 行業(yè)網(wǎng)站開發(fā)運營方案免費推廣平臺排行
  • 煙臺h5網(wǎng)站制作sem優(yōu)化和seo的區(qū)別