python通俗講解閉包
通俗理解閉包
先來看看什麼是閉包吧
閉包是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
這句話閉包是由函數和與其相關的引用環境組合而成的實體,我覺得已經能概括閉包的概念了。下面看看分析
先看一個最簡單的例子
def outer_func():
outer_list = []
def inner_func():
outer_list.append(1)
print out_list
return inner_func
func1 = outer_func()
func1() #[1]
func1() #[1,1]
func2 = outer_func()
func2() #[1]
func2() #[1,1]
這個例子說明閉包與一般的函數不一樣,他擁有的“環境”是獨一份的。其中的outer_list稱爲自由變量,既不是全局變量又不是本地變量。
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.
這種特性類似 類與實例 的關係,函數outer_func就像是一個類,執行func1 = outer_func就像是創建了一個實例,而實例func1能夠繼承類的屬性,這裏也可以看作是繼承oucter_func的環境。
下面我換一種寫法(這種寫法是sml的寫法。local與in之間就是環境)
local
outer_list = []
in
def inner_func():
outer_list.append(1)
print out_list
end
函數outer_func將環境outer_list = []與函數inner_func捆綁在一起,它的作用僅此而已
下面爲了加深理解,我們再看一個閉包陷阱
def outer_func():
func_list = []
for i in xrange(3):
def inner_func():
print i
func_list.append(inner_func)
return func_list
fun1,fun2,fun3 = outer_func()
fun1() #2
fun2() #2
fun3() #2
我們再來通過拆分環境和函數來分析outer_func
執行fun1,fun2,fun3 = outer_func()之後,執行fun1()之前的環境
local
func_list = [inner_func1, inner_func2,inner_func3]
i = 2 #"環境初始化"完成之後,i就是2
in
def inner_func():
print i
end
這樣可以看出i明顯是2,但下面稍加改動
def outer_func():
func_list = []
for i in xrange(3):
def inner_func(_i = i): #寫入默認參數
print _i
func_list.append(inner_func)
return func_list
fun1,fun2,fun3 = outer_func()
fun1() #0
fun2() #0
fun3() #2
分析上面的程序
這裏展示func_list中 第一個 inner_func(func1)的環境
local
func_list = [inner_func1, inner_func2,inner_func3]
i = 2 #外部的i還是2
in
def inner_func(_i = 0): #對於inner_func1,_i=0,這裏可以發現形參_i及時捕獲i=0時的值,當作默認參數
print _i
end
爲什麼這時候func1中的i=0呢?這是因爲inner_func有了參數_i,它能在程序執行func_list.append(inner_func)的時候,
會創建相關”函數實例“,而該函數定義中有一個帶默認值的形參_i,注意python可以指定一個變量作爲函數參數的默認值,
因此它會在創建的時候也記錄下i此時的值,即0.
而不帶參數的inner_func,它只會在outer_func完全運行結束之後,讀取外部環境中i的值,即i=2。
下面說說閉包的應用
修飾函數
能在不改動已有函數內部構造的同時,添加額外功能,如檢錯功能。
閉包使得先執行wrapper函數再執行func,可以控制函數執行的先後。
def func_dec( func ):
def wrapper( *args ):
if len(args) < 2:
print "less argument"
else:
func( args )
return wrapper
@func_dec
def mySum(*args):
print sum( *args )
mySum(2) #"less argument"
mySum(1,2,3) #6
分析一下
以mySum(2)爲例子
local
func = mySum
in
def wrapper( *args ): #args = 2
if len(args) < 2:
print "less argument"
else:
func( args )
end
這樣看思路應該清晰不少
這裏的 mySum(2) 等價於 func_dec( mySum )(2),func_dec後面接了2個括號,其實也可以看出func_dec必定返回一個函數。
之所以搞得這麼麻煩,就是爲了讓使用mySum的時候附帶一個檢測參數個數的功能,前提是不改變mySum原有代碼。類似接口函數。
上面的說mySum(2) 等價於 func_dec( mySum )(2),由此會產生一些隱晦的bug
,看看下面的例子:
def counter( cls ):
obj_list = []
def wrapper( *args, **kwargs ):
new_obj = cls( *args, **kwargs )
obj_list.append( new_obj )
print "class: %s' object number is %d" % (cls.__name__, len(obj_list) )
return new_obj
return wrapper
@counter
class my_cls( object ):
STATIC_MEN = "static"
def __init__( self, *arg, **kwargs):
print self, arg, kwargs
print my_cls.STATIC_MEN
my_cls() #AttributeError: 'function' object has no attribute 'STATIC_MEN'
爲什麼會說'function' object has no attribute 'STATIC_MEN'呢?
首先確定語句出錯的位置:print my_cls.STATIC_MEN
那爲什麼my_cls不存在屬性STATIC_MEN呢?
這是因爲使用閉包後(@語法糖),my_cls() = counter(my_cls)()
這裏應該被做了類似重定向的操作(因爲語法糖@counter的緣故), 此時my_cls不再是原來的class,
執行的時候my_cls這個名字被指向了counter(my_cls), 即wrapper函數。
可以打印看看print my_cls.__name__ #顯示wrapper
這也是爲什麼能直接使用my_cls()的原因,因爲它已經不再是原來的類,而是新的函數wrapper。
因此需要將my_cls.STATIC_MEN修改爲self.STATIC_MEN,畢竟執行的時候my_cls已經不再是原來的my_cls了
要是還想通過my_cls訪問靜態屬性,嘗試以下方法
def counter(cls):
obj_list = []
@functools.wraps(cls)
def wrapper(*args, **kwargs):
... ...
return wrapper
對wrapper使用functools進行了一次包裹更新,使經過裝飾的my_cls看起來更像裝飾之前的類或者函數。
該過程的主要原理就是將被裝飾類或者函數的部分屬性直接賦值到裝飾之後的對象。
如WRAPPER_ASSIGNMENTS(name, module and doc, )和WRAPPER_UPDATES(dict)等。
但是該過程不會改變wrapper是函數這樣一個事實。
my_cls.__name__ == 'my_cls' and type(my_cls) is types.FunctionType
單例模式:https://www.cnblogs.com/yssjun/p/9858420.html