對python的閉包,裝飾器的理解

先來看一段簡單的代碼

def add():
        a = 2
        b = 5
        def func(c):
            return a*c+b
        return func

在函數func裏面用到了外面的參數a, b(稱爲函數的環境變量)。像這樣,函數與其環境變量就構成了閉包。簡單地理解呢,就是嵌套函數。

通常地,函數被調用之後其命名空間隨着函數的結束而結束,但是在閉包中不然,外部函數的命名空間和內部的結合到了一起。

當我們調用外層函數時,返回內層函數,特別注意!!返回的是一個函數

續上面的代碼

a = add()
print a 
# <function func at 0xb70c9f7c>
a(5)  # output: 15

可見調用add後,返回func函數,我們讓a指向了這個新函數,直接print的話並沒有結果,唯有我們再次調用的時候才計算。

下面我們通過一個例子來了解一下閉包使用時的注意事項

def count():
    fs = []
    for i in range(1, 4):
        def f():
            return  i * i
        fs.append(f)
    return fs
f1, f2, f3 = count()
print f1, f2, f3  # 9 9 9 

調用count()的時候便返回三個函數,都放在fs的列表裏,最後由f1,f2,f3分別指向。但是爲什麼我們打印出來的時候不是:1,4,9而是9,9,9呢。
這就是我們使用閉包時要注意的地方了!!返回來的是函數而不是計算結果,而其中的i值仍然在自增,當我們打印的時候,i已經是3了,我們調用f1,f2,f3的時候才真正進行了運算,這時運算的數據是3。所以,當我們使用閉包的時候要注意內部函數不要使用循環變量
如果一定要用的話,也不是沒有辦法,再加一個函數“記住”變量值就好了。像這樣

def count():
    fs = []
    for i in range(1, 4):
        def f(j):
            def g():
                return j * j
            return g
        fs.append(f(i))
    return fs
f1, f2, f3 = count()

這時候,f1,f2,f3對應的是被返回來的f(1), f(2), f(3)
好了,瞭解閉包後我們就可以涉及一下裝飾器了。所謂裝飾器就是實現一些小功能(比如在函數操作前後輸出打印點什麼),又能不讓代碼嵌入其中。通常我們用@語法把裝飾器,就是實現小功能的函數放在另一個函數的上一行。

例1
def log(func):
    def wrapper(*args, **kw):
        print 'call %s()' % func.func_name
        return func(*args, **kw)
    return wrapper
@log  # now* = log(now)
def now():
    now.func_name = "now"
    print "2016-7-17"
now()  # 調用的是now*
#call now
#2016-7-17

“調用的是now*”的意思是這時的now指向的是由log()函數返回來的wrapper()函數。下面的例子寫一個三層循環,可以打印附加文字。

例2
def log(text):
    def decorator(func):
        def wrapper(*arg, **kw):
            print '%s %s()' %(text, func.func_name)
            return func(*arg, **kw)
        return wrapper
    return decorator

@log('execute')
def time():
    time.func_name = "time"
    print "time for now is 2016.7.17"
time() 
#execute time
#2016.7.17

如果再加一點修改,可以把上面兩個例子:例1和例2整合
calllable()判斷是否可調用。顯然,對象是函數的話返回true,字符串的話返回false

def log2(text):  # text有可能是文本,也可能是函數
    if callable(text):  # if callable(text)=true then text 是函數
        def wrapper(*args, **kw):
            print 'begin call: ' + text.func_name
            text(*args, **kw)
            print 'end call'
        return wrapper
    else:  # if false, text是文本,接下來是三層循環
        def decorator(func):
            def wrapper(*args, **kw):
                print "begin call: " + text
                func(*args, **kw)
                print 'end call: '
            return wrapper
        return decorator

最後我們來看兩個稍複雜的例子加深印象和理解:

下面例子中,我定義了全局變量base,但是com方法中又定義了,最後wrapper函數使用的會是重新定義了的這個base(=7)。當定義了一個函數時,函數內的變量構成該函數的名字空間,前面提到的:在閉包裏面,內部函數的命名空間與外部函數結合到了一起,這也是一種將命名空間靜態化的方法。

base = 2
def com(func):
    base = 7
    # print "enter com"
    def wrapper(value):
        if value > base:
            print "wrapper " + str(base)  # wrapper 7
            return func(value)
        else:
            return func(base)
    return wrapper


@com
def sub_ten_sqart(value):
    return math.sqrt(value - 7)

 print sub_ten_sqart(11)  # 2.0
 print sub_ten_sqart(5)  # 0.0

你覺得下面這段代碼的輸出會是怎樣的呢?

def demo_1(func):
    print "enter demo_1"
    def wrapper(a, b):
        print "enter demo_1's wrapper"
        func(a, b)
    return wrapper
def demo_2(func):
    print "enter demo_2"
    def wrapper(a, b):
        print "enter demo_2's wrapper"
        func(a, b)
    return wrapper

@demo_1
@demo_2
def add_func(a, b):
    print "the result is %d" %(a+b) 

add_func(1, 2)

輸出是

"enter demo_2"
"enter demo_1"
"enter demo_1's wrapper"
"enter demo_2's wrapper"
"the result is 3"

你對了嗎~

當被兩個裝飾器修飾時,從最裏層開始往外應用。
也就是說,過程相當於是這樣的:
add_func = demo_2(add_func)
打印輸出”enter demo_1”並且此時add_func變成了 wrapper(add_func)函數,但是該wrapper裏面還未執行。
緊接着:add_func = demo_1(add_func)=demo_1(wrappeer(add_func))
打印”enter demo_2”,傳遞給demo_1裏面的wrapper()的add_func其實是來自demo_2的wrapper,所以在demo_1的wrapper裏面,先打印”enter demo_1’s wrapper”,最後才執行了demo_2中的wrapper。

所以,對於Python的裝飾器,要記住它被返回的是一個函數,是裝飾器裏面定義的另一個函數!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章