1. 程式人生 > >python中的閉包!

python中的閉包!

目錄

1. 函式

python中的一切皆物件,函式也是一個物件,如下:

In [44]: def test():
 ....: pass
 ....: 
In [45]: type(test)
Out[45]: function
In [46]: type(test.__call__)
Out[46]: method-wrapper

如果一個物件能使用函式呼叫的方式呼叫(即xxx()的方式),那麼這個物件需要定義一個__call__的魔法屬性,如下:

In [47]: class Foo(object):
 ....: def __call__(self):
 ....: print("__call__")
 ....: 
In [48]: f = Foo()
In [49]: f()
__call__

在Foo物件中定義了一個__call__的魔法屬性,建立了Foo的一個實現物件f,並使用呼叫:f(),如果傳遞一個函式物件的引用,那麼通過這個函式的應用就能呼叫這個函式。

2. 什麼是閉包?

假如要計算反正y=ax+b的值,有如下幾種做法:

2.1 直接計算

# 第1種
a = 1
b = 2
y = k*x+b

缺點:如果需要多次計算,那麼就的寫多次y = a*x+b這樣的式子

2.2 使用函式

# 2. 使用函式
def test(a, b, x):
 print(a * x + b)
test(1, 2, 1)
test(1, 2, 10)
test(2, 4, 1)
test(2, 4, 10)

缺點: 如果要多次計算某條y=ax+b上的值,需要多次傳入a,b的值

2.3 使用全域性變數

# 3. 使用全域性變數
a = 1
b = 2
def test(x):
 print(a * x + b)
test(1)
test(10)
a = 2
b = 4
test(1)
test(10)

缺點:如果要計算多條y=ax+b上的值,需要多次修改全域性變數a,b的值,麻煩

2.4 使用預設引數

# 4. 使用預設引數
def test(x, a=1, b=2):
 print(a * x + b)
test(1)
test(10)
test(1, a=2, b=4)
test(10, a=2, b=4)

優點:相比第三種方式,a和b是預設引數的一部分,不是全域性變數

缺點:如果計算多條y=ax+b的值,需要多次傳入a,b的值麻煩

2.5 使用例項物件

# 5. 使用例項物件
class Test(object):
 def __init__(self, a, b):
 self.a = a
 self.b = b
 def __call__(self, x):
 print(self.a * x + self.b)
t1_2 = Test(1, 2)
t1_2(1)
t1_2(10)
t2_4 = Test(2, 4)
t2_4(1)
t2_4(10)

缺點,為了計算y的值,需要儲存a,b的值,用了例項物件,浪費資源

2.6 使用閉包

# 6. 使用閉包
def test(a, b):
 def create_y(x):
 print(a * x + b)
 return create_y
 
t1_2 = test(1, 2)
t1_2(1)
t1_2(10)
t2_4 = test(2, 4)
t2_4(1)
t2_4(10)

函式create_y與變數a,b構成閉包。在建立閉包的時候,我們通過test的引數a,b說明了這兩個變數的取值,這樣,我們就確定了函式的最終形式(y = x + 2和y = 2x + 4)。我們只需要變換引數a,b,就可以獲得不同的直線表達函式。由此,我們可以看到,閉包也具有提高程式碼可複用性的作用。

進群:960410445 即可獲取原始碼!

如果沒有閉包,我們需要每次建立直線函式的時候同時說明a,b,x。這樣,我們就需要更多的引數傳遞,也減少了程式碼的可移植性。

關於閉包,維基百科的定義如下:

在電腦科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體。

百度百科:

閉包是可以包含自由(未繫結到特定物件)變數的程式碼塊;這些變數不是在這個程式碼塊內或者任何全域性上下文中定義的,而是在定義程式碼塊的環境中定義(區域性變數)。“閉包” 一詞來源於以下兩者的結合:要執行的程式碼塊(由於自由變數被包含在程式碼塊中,這些自由變數以及它們引用的物件沒有被釋放)和為自由變數提供繫結的計算環境(作用域)。在 Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python和Lua,objective c 等語言中都能找到對閉包不同程度的支援。

在程式設計領域我們可以通俗的說:子函式可以使用父函式中的區域性變數,這種行為就叫做閉包!

3. 修改閉包中的資料

python3中:

x = 100
def test1():
 x = 101
 def test2():
 nonlocal x
 print("--1--x: %s" % x)
 x = 102
 print("--2--x: %s" % x)
 return test2
test1()()

像在函式中修改全域性變數一樣,在python3中修改閉包中的資料需要使用 nonlocal ,就像上面那麼: nonlocal x

python2中:

def test1():
 x = 101
 temp_list = [x]
 def test2():
 print("--1--x: %s" % temp_list[0])
 temp_list[0] = 102
 print("--2--x: %s" % temp_list[0])
 return test2
test1()()

由於python2沒有像python3中的 nonlocal ,要在python2中修改閉包中的資料,只能在一個list中儲存要修改的變數,直接對該列表中對應的資料進行修改就能完成偽修改。