1. 程式人生 > >Python基礎(10):返回函式,閉包以及裝飾器之間那些糾纏不清的關係

Python基礎(10):返回函式,閉包以及裝飾器之間那些糾纏不清的關係

一:返回函式

定義:函式可以作為另一個函式的返回值。

理論來源:函式可以巢狀定義。

def fun1(li):
    def fun2():
        return sum(li)
    return fun2

呼叫fun1函式,返回的是一個一個fun2例項。

獲取最終結果,需要執行:

test()

二:閉包

定義:在一個內部函式裡,對外在作用域(不是全域性作用域)的變數進行引用,這種程式結構被稱為閉包。

舉例:上例中的fun2是一個內部函式,它引用了li這個外部變數,li的作用域僅在fun1中,所以fun2,就是一個閉包。

閉包和返回函式的關係:閉包以返回函式的形式實現。

注意:閉包,是函式體和引用環境的整體。

警告:閉包中儘量不要引用迴圈變數,或後期會發生變化的變數。

例項:

def fun3():
    f=[]
    for i in range(1,4):    #迴圈遍歷1,2,3
        def fun4():
            return i**2     #返回i的二次冪
        f.append(fun4)      #將結果追加到列表f中
    return f

測試程式碼如下:

for i in fun3():
    print(i())

原以為會返回一個[1,4,9]的列表,但是結果為[9,9,9]

這是因為,其實f.append(fun4)這行程式碼,不是將結果追加到了列表中,而是將fun4函式追加到列表中。

最終的列表f,每一個元素,都是一個閉包。

閉包是函式和執行環境的結合,在這裡,函式是i**2,而環境,就是i。當三個元素被追加完時,i已經變成了3。

所以,列表f的每一個元素都是3**2,也就是9

解決這類問題的方向是:增加一層隔離環境。

通過上面的例子我們明白,結果出現偏差的主要原因是,執行環境發生變化(i發生了變化)。那麼,可以這樣解決:

def fun3():
    f=[]
    def fun4(j):
        def fun5():
            return j ** 2  # 返回i的二次冪
        return fun5        #返回一個閉包,函式為j**2,執行環境為j
    for i in range(1,4):    #迴圈遍歷1,2,3
       f.append(fun4(i))     #追加元素到f,每一個元素為一個閉包,在閉包內,變數恆為i
    return f

三:裝飾器

定義:在程式碼執行期間,動態增加功能的方式,成為裝飾器。

本質:裝飾器本質上是一個返回函式的高階函式。

與閉包的關係:是閉包的一種應用場景。

定義裝飾器的兩種方法:

1:利用閉包原理

雙層巢狀:定義一個裝飾器,接收函式作為引數,返回一個warpper閉包,在warpper內部,定義在函式執行前或後進行的操作。

例項:

import time
def log(fun):                           #定義一個裝飾器,接收函式fun為引數
    def wrapper(*args,**kwargs):        #內部函式wrapper,定義具體的裝飾操作
        print('excute %s'%fun.__name__) #在函式執行前,列印excute 函式名
        return fun(*args,**kwargs)      #執行函式
    return wrapper
@log                                   #用@為函式新增裝飾器,相當於abc=log(abc)
def abc():                            #abc=log(abc),執行wrapper閉包,首先列印excute abc
    time.sleep(1)                      #執行函式本身,等待一秒
    print('my name is abc')           #執行列印方法
if __name__=='__main__':
    abc()

三層巢狀:適用於裝飾器本身需要傳入引數。

def log(text):                          #定義一個裝飾器,接收普通引數text
    def decorator(fun):                #定義一個高階函式,接收函式為引數
        def wrapper(*args,**kwargs):        #內部函式wrapper,定義具體的裝飾操作
            print('%s %s'%(text,fun.__name__)) #在函式執行前,列印text 函式名
            return fun(*args,**kwargs)      #執行函式
        return wrapper                  #返回wrapper閉包
    return decorator                    #返回decorator閉包
@log('ready,go!')                      #用@為函式新增裝飾器,並攜帶引數,相當於abc=log('ready go!')(abc)
def abc():                            #abc=log('ready go!')(abc),執行decorator閉包,再執行wrapper閉包,首先列印ready go! abc
    time.sleep(1)                      #執行函式本身,等待一秒
    print('my name is abc')           #執行列印方法

2:類裝飾器

class log(object):
    def __init__(self,fun):                 #例項化需要接收一個函式
        self.fun=fun                         #將函式賦給類的fun屬性,相當於接收一個函式作為引數
    def __call__(self, *args, **kwargs):    #能像方法一樣被呼叫
        print('excute %s'%self.fun.__name__)#在函式執行前列印
        return self.fun()                   #執行函式
@log                                        #abc=log(abc),將abc作為log類的一個例項
def abc():                                 #abc已經變成一個Log物件,呼叫__call__方法,先列印excute abc
    time.sleep(1)                           #睡眠
    print('my name is abc')                #列印my name is abc

在有多個裝飾器裝飾同一個函式時,離函式近的裝飾器先被裝飾。