1. 程式人生 > >聊聊Python中的閉包和裝飾器

聊聊Python中的閉包和裝飾器

+= color 我們 註意 引用調用 out 技術 obj 外部

1. 閉包

首先我們明確一下函數的引用,如下所示:

def test1():
    print("--- in test1 func----")

# 調用函數
test1()

# 引用函數
ret = test1

print(id(ret))
print(id(test1))

#通過引用調用函數
ret()

運行結果:

--- in test1 func----
140212571149040
140212571149040
--- in test1 func----

以y=kx+b為例,請計算一條線上的某個點,即給x值計算出y值。下面以這個例子引出閉包的概念。

方法1

# 第1種
k = 1
b = 2
y = k*x+b
# 缺點:如果需要多次計算,那麽就的寫多次y = k*x+b這樣的式子

方法2

# 第2種
def line_2(k, b, x):
    print(k*x+b)

line_2(1, 2, 0)
line_2(1, 2, 1)
line_2(1, 2, 2)
# 缺點:如果想要計算多次這條線上的y值,那麽每次都需要傳遞k,b的值,麻煩

方法3

# 第3種: 全局變量
k = 1
b = 2
def line_3(x):
    print(k*x+b)

line_3(0)
line_3(
1) line_3(2) k = 11 b = 22 line_3(0) line_3(1) line_3(2) # 缺點:如果要計算多條線上的y值,那麽需要每次對全局變量進行修改,代碼會增多,麻煩

這裏引申一下,關於全局變量,要是直接讀取,不修改的話,是不用加global的。

而且所謂的修改,指的是地址變了,假如我指向的是一個列表,指向沒變,列表中的內容改變了,也不用加global的。

我們看一下下面的例子:

a = 100
b = chenchi
c = [1, 2, 3]

def func():
    print(a的值:{},a的地址:{}
.format(a, id(a))) print(b的值:{},b的地址:{}.format(b, id(b))) print(c的值:{},c的地址:{}.format(c, id(c))) def func2(): global a global b a += 1 b += ccc # 這裏c不用聲明global,c的地址沒有發生變化 c.append(4) if __name__ == __main__: func() func2() func()

運行結果:

技術分享圖片

列表的地址沒變,不用加global。

方法4

# 第4種:缺省參數
def line_4(x, k=1, b=2):
    print(k*x+b)

line_4(0)
line_4(1)
line_4(2)

line_4(0, k=11, b=22)
line_4(1, k=11, b=22)
line_4(2, k=11, b=22)
# 優點:比全局變量的方式好在:k, b是函數line_4的一部分 而不是全局變量,因為全局變量可以任意的被其他函數所修改
# 缺點:如果要計算多條線上的y值,那麽需要在調用的時候進行傳遞參數,麻煩

方法5

# 第5種:實例對象
class Line5(object):
    def __init__(self, k, b):
        self.k = k
        self.b = b

    def __call__(self, x):
        print(self.k * x + self.b)


line_5_1 = Line5(1, 2)
# 對象.方法()
# 對象()
line_5_1(0)
line_5_1(1)
line_5_1(2)
line_5_2 = Line5(11, 22)
line_5_2(0)
line_5_2(1)
line_5_2(2)
# 缺點:為了計算多條線上的y值,所以需要保存多個k, b的值,因此用了很多個實例對象, 浪費資源

關於__call__()的用法:

技術分享圖片

方法6

采用閉包的方式,什麽是閉包呢?

在函數內部再定義一個函數,並且這個函數用到了外邊函數的變量,那麽將這個函數以及用到的一些變量稱之為閉包。

如下所示:

# 第6種:閉包
def line_6(k, b):
    def create_y(x):
        print(k*x+b)
    return create_y


line_6_1 = line_6(1, 2)
line_6_1(0)
line_6_1(1)
line_6_1(2)
line_6_2 = line_6(11, 22)
line_6_2(0)
line_6_2(1)
line_6_2(2)

閉包,紅色部分return的不能加(),不加()是函數引用,否則就應該是函數的返回值。

在上面的例子中,函數line_6與變量k,b構成閉包,閉包具有提高代碼可復用性的作用。

由於閉包引用了外部函數的局部變量,則外部函數的局部變量沒有及時釋放,消耗內存,但是相對於實例對象來說,已經好多了。

技術分享圖片

思考:函數、匿名函數、閉包、對象 當做實參時 有什麽區別?
1. 匿名函數能夠完成基本的簡單功能,傳遞是這個函數的引用 只有功能。
2. 普通函數能夠完成較為復雜的功能,傳遞是這個函數的引用 只有功能。
3. 閉包能夠將較為復雜的功能,傳遞是這個閉包中的函數以及數據,因此傳遞是功能+數據。
4. 對象能夠完成最為復雜的功能,傳遞是很多數據+很多功能,因此傳遞是功能+數據。

修改外部函數中的變量

x = 300
def outer():
    x = 200
    def inner():
        nonlocal x
        # global x
        print(---1---x=%d % x)
        x = 100
        print(---1---x=%d % x)
    return inner

t1 = outer()
t1()

註意,這是python3,輸出結果如下:

技術分享圖片

如果把nonlocal改成global,第一個x就變成300了。

2. 裝飾器

聊聊Python中的閉包和裝飾器