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捆綁在一起,它的作用僅此而已
下面為了加深了解,我們再看一個閉包陷阱
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()之前的環境
func_list = [inner_func1, inner_func2,inner_func3]
i = 2 #"環境初始化"完成之後,i就是2
def inner_func():
print i
這樣可以看出i明顯是2,但下面稍加改動
func_list = []
for i in xrange(3):
def inner_func(_i = i): #寫入預設參數
print _i
func_list.append(inner_func)
return func_list
fun1() #0
fun2() #0
分析上面的程式
這裡展示func_list中 第一個 inner_func(func1)的環境
func_list = [inner_func1, inner_func2,inner_func3]
i = 2 #外部的i還是2
def inner_func(_i = 0): #對于inner_func1,_i=0,這裡可以發現形參_i及時捕獲i=0時的值,當作預設參數
print _i
為什麼這時候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)為例子
func = mySum
def wrapper( *args ): #args = 2
if len(args) < 2:
print "less argument"
else:
func( args )
這樣看思路應該清晰不少
這裡的 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參考:
https://www.cnblogs.com/yssjun/p/9887239.html原文位址
https://www.cnblogs.com/friedCoder/p/12697364.html