02Python學習筆記之二.四【閉包、裝飾器】2019-08-17

章節號 內容????????????
1圖片格式(png) 寬度大于620px,保持高寬比減低為620px
1-1 應用
1-1-1 方法

第1章節? 閉包

  • 1-1?閉包—解釋

??↓函數引用:

In [84]: def test():
    ...:     print("1111111111111111")
    ...:     

In [85]: test
Out[85]: <function __main__.test>

In [86]: id(test)
Out[86]: 140017952235032

In [87]: b=test

In [88]: b()
1111111111111111

??函數名就是一個指向函數塊地址的變量,這個變量就可以賦值給別的變量。
??閉包:一個函數內部又定義一個函數,而且內部函數使用到了外部函數的變量(形式參數),則外部的函數的參數和內部的函數統一構成一個閉包
??作用:

def test(number):
    print(1)
    def testin():
        print(2)
        print(number+100)
    print(3)
    return testin
    
test(10000)
1
3

??↑可以看到,如上的函數,內部的那個函數并未執行。為什么呢?因為明顯只調用了test(),而沒有哪個地方調用了testin()。


??↑具體流程如上:
??1、python解釋器運行到1的箭頭處,得知這里是一個函數的定義,則把這一塊代碼的首地址和test綁定。直接跳轉到2處,因為沒有調用函數,函數體是不執行的。
??2、從2這里開始跳入函數體開始執行。
??3、執行箭頭3的打印語句。
??4、遇到了內部的函數定義。為testin賦值。
??5、直接跳出內部函數的定義,來到箭頭5,執行打印語句。
??6、執行返回語句。
??執行到了這里,test(10000)這一串字符,是否就代表了內部函數的引用呢?
??加上一對括號()測試一下:

def test(number):
    print(1)
    def testin():
        print(2)
        print(number+100)
    print(3)
    return testin
    
test(10000)()
1
3
2
10100

??↑可以看到內部函數已經被調用了。

??關鍵點在于:

??里面的函數在調用時,保存了外部函數傳入的參數。從某種程度上來說,調用內部函數的時候,外部函數已經執行完畢了,這個參數應該已經度過了自己的生命周期,但是因為這是一個閉包,所以這個參數還能被內部函數繼續使用。

def test(number):
    print(1)
    def testin(number1):
        print(2)
        print(number+100+number1)
    print(3)
    return testin
    
test(10000)(1)

??↑我們為內部函數再加上一個形式參數,則調用時候也要作相應改變。

1
3
2
10101

??小結:外部函數的參數是基數,內部函數的參數是變數
??特點:外部函數的返回值,是內部函數的一個引用。

  • 1-2?閉包—應用

def line(a,b):
    def fx(x):
        return a*x+b
    return fx

print(line(1,4)(3))

??來看這個閉包的應用,注意看內部的返回值,a*x+b,這其實就是一個斜截式的直線方程,求的是y值。f(x)=a*x+b

7

??↑來看調用后的答案。
??當我第一次接觸閉包的應用的時候,其實我內心是拒絕的。我們仔細來看這個調用:

print(line(1,4)(3))

??大致,我覺得這句代碼就是,調用2次函數,傳入3個參數。對不對?
??那么,這樣做從表面來看,是否是多此一舉呢?我們考慮用如下方式來達到相同目的:

def line(x,a,b):
    return a*x+b

print(line(3,1,4))
7

??是不是這樣程序更清晰呢?
??但是,有這樣一個問題,讓我們從這方面來考慮:
??f(x)=a*x+b這個方程組,a和b值是常數,即我們計算這個方程的時候,對于不同的x值,a和b只需要確定一次就行了,其余的時候我們只用改變x的值,就能求得不同的y值。
??考慮到這個問題,如果我們在a和b值一定的情況下,計算多個x值對應的y,那么大概就是如下的樣子:

print(line(1,1,4))
print(line(2,1,4))
print(line(3,1,4))
print(line(4,1,4))
print(line(5,1,4))
print(line(6,1,4))
#等等

??對了,你大概注意到了,1和4,是否沒必要傳遞那么多次。有辦法解決嗎?想一想閉包。

def line(a,b):
    def fx(x):
        return a*x+b
    return fx


fx=line(1,2)

print(fx(1))
print(fx(2))
print(fx(3))
print(fx(4))
print(fx(5))
print(fx(6))

??是不是感覺,為了一個簡潔,省力,真是無所不用其極呀!

第2章節?裝飾器

  • 2-1?裝飾器—工作原理

??↓首先從這段代碼起步

In [1]: def func():
   ...:     print("func")
   ...:     

In [2]: func
Out[2]: <function __main__.func>

In [3]: func()
func

In [7]: fun = lambda x:x+1

In [8]: fun(1)
Out[8]: 2

In [9]: fun=lambda x,y:x+5*y

In [10]: fun(1,2)
Out[10]: 11

??↓然后是這段代碼:

def func():
    print("func1")

def func():
    print("func2")

func()
func2

??↑python從上到下解析,上面func的地址被下面的func覆蓋了,所以執行的是最后一個。
??下面開始需求提出及分析思路,假設類似多個函數如下:

def func1():
    print("func1")

def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")
.
.
.

??后因程序需要,每個函數需要加入一個功能N,則我們如何解決?顯而易見的方式為:

def func1():
    print("func1")
    功能N

def func2():
    print("func2")
    功能N    

def func3():
    print("func4")
    功能N

def func4():
    print("func4")
    功能N
.
.
.

??但是如果這樣的函數有成百上千個,那么一是工作量太大,二是相同的冗余代碼將大幅增多。有什么改進方式嗎?可以考慮如下:

def function():
    功能N

def func1():
    print("func1")
    function()  

def func2():
    print("func2")
    function()  

def func3():
    print("func4")
    function()  

def func4():
    print("func4")
    function()  
.
.
.

??是不是感覺突然變得很美好?
??但是,編寫代碼應該遵循的原則是,能擴不改老舊不動。在函數里加了調用,某種意義上還是修改了代碼,可能造成不可描述的問題。考慮如下改動:

def zsq(func):
    print("gong neng 1 diao yong")
    func()

def func1():
    print("func1")

zsq(func1)

??↑寫一個新函數,把功能N在新函數中實現,這個新函數有個參數,傳遞的是函數的引用,則可以在新函數中調用老函數。但是這樣有一個問題,你調用的方式發生了改變:從調用func1變成了調用zsq(func1),這有什么問題呢???就是要大量修改原來代碼中寫的func1()!!!!!!
??思考解決辦法。
??核心問題在哪里,那就是我們要完成一個等式,形如:
??func1() = zsq(func1)
??如果我們在func1()調用之前,改變func1指向的的位置為zsq的位置,在處理好zsq接受的func1的參數問題,那不就實現了不修改源代碼中func1()的寫法,也實現了功能的改變了嗎?
??如這個樣子func1= zsq(func1)
??func1()
??有什么感覺了嗎?這是不是相當于,調用了一個接收參數的函數(zsq(func1)),這個函數有一個返回值,并且把返回值賦值給了func1。
??那么關鍵來了,現在的核心問題就是:
??1、zsq這個函數,必須有一個返回值,這個返回值是一個函數。
??2、zsq這個函數,必須接把接受到的參數保存下來,留給下一個函數調用的時候來是使用。什么函數能在執行完畢后保存住一個傳入的變量?什么函數的返回值是一個函數??
??閉包!!!!!!!!!!!!!!!!!!!!!!!!

#首先確定第一條:
def zsq():
    pass
    pass
    pass
    return function
#參考原函數:
def zsq(func):
    print("gong neng 1 diao yong")
    func()
#擬改寫成:
def zsq(func):
    print("gong neng 1 diao yong")
    func()
    return function


#根據以下代碼,確定第二條:參數要保存下來,那就要函數內定義個一個函數,在內函數中顯式寫出func,形成閉包
def zsq(func):
    def  XXXX():
        print("gong neng 1 diao yong")
        func()
    return function

??↑這個內函數應該叫什么名字呢?我們知道,zsq(func)是要返回一個函數給func1的,然后使用func1()來調用一個函數,而且這個函數必須要包含原來func1()的功能,是func1()的一個超集,那滿足這個功能的,不正是XXXX()函數么?既然我們之前寫了return function,那么XXXX就正好改為function。

def zsq(func):
    def  function():
        print("gong neng 1 diao yong")
        func()
    return function

??↓完整的定義和調用如下:

#完整調用
def zsq(func):
    def function():
        print("gong neng diao yong")
        func()
    return function

def func1():
    print("func1")

def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")

func1=zsq(func1)
func1()
gong neng diao yong
func1

??好的,剛才我們耗費了大量的精力,完成了一種在不改變原有函數名稱的情況下,改掉了函數調用內容的方法,不可謂不精巧,不可謂不奇妙。但是直到現在,之前的需求還是沒有完全滿足,因為到現在為止,這個方法還是需要顯式的調用func1=zsq(func1)一次,才能完成預想的任務。那么有很多個funcN的時候,不是一樣需要手動調用嗎?
??↓別急,python已經為我們做了隱藏細節的處理,剛才的代碼,我們只要稍加改動,就能完成預想的效果:

def zsq(func):
    def function():
        print("gong neng diao yong")
        func()
    return function

@zsq
def func1():
    print("func1")

def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")


# func1=zsq(func1)
func1()
gong neng diao yong
func1

??↑其中,函數名稱上方的@XXX,就叫做裝飾器

  • 2-2?裝飾器—2個裝飾器

??裝飾順序:最靠近函數的最先解析,最原理函數的最后解析。
??why?
??代碼解析,自上而下。當解析到@zsq1時,python就要準備為下面的函數進行裝飾了,但是下面是不是函數?不是!
??下面是另一個裝飾器@zsq2,所以python只能跳過@zsq1,繼續看@zsq2能不能執行裝飾,即@zsq2下面緊跟的是函數還是其他的裝飾器,這里是函數,所以就先解析@zsq2了。

@zsq1
@zsq2
def name():
    print("this is name function")
    return "name"

print(name())

??↓以下假設調用的函數為func1 (),解析過程大致為:


??↑1、首先解析@zsq,此時函數func1指向print("func1")


??↑2、此時函數已經自動調用并跳轉至zsq1函數內,python自動把func1的值賦給了funcY(這是裝飾器的工作機制),因此funcY指向print("func1")

??↑3、函數zsq1函數內只有一條能執行的語句return,執行完return則zsq1函數執行完畢。這相當于python進行的自動調用zsq1完畢了,那么就要返回并執行func1()

??↑4、跳出zsq1函數后,@zsq1功能已完成,這里我們暫時把@zsq1蓋住不看。跳出zsq1函數后,func1接收了zsq1函數的返回值(python解釋器自動進行的操作),實際上func1的指向已經發生了改變,指向的是原zsq1內部函數的代碼,如圖所示(注意這里手誤把“lalala gong neng2”錯寫成了“gong neng diaoyong 2”)。

??↑5、當系統想要開始執行func1()的代碼時,發現還有一個裝飾器@zsq,則繼續開始解析@zsq。python將自動調用zsq函數。同時把當前的func1的值傳遞給了funcX

??↑6、這里馬上要執行return語句返回。


??↑7、zsq函數返回后,func1的指向再次改變為zsq函數的內部函數為↓

        print("gong neng diao yong1")
        funcX()

??由上所知,funcX指向

        print("lalalal gong neng2")
        funcY()

??由上所知,funcY指向

         print("func1")

??那么我們做一個等式的替換,把上述3段代碼進行融合:

        print("gong neng diao yong1")
        funcX()
        print("lalalal gong neng2")
        funcY()
        print("func1")
        print("gong neng diao yong1")
        print("lalalal gong neng2")
        print("func1")
li@li-ThinkPad-T420s:~/Desktop/py$ cd /home/li/Desktop/py ; env PYTHONIOENCODING=UTF-8 PYTHONUNBUFFERED=1 /usr/bin/python3 /home/li/.vscode/extensions/ms-python.python-2019.8.30787/pythonFiles/ptvsd_launcher.py --default --client --host localhost --port 39427 /home/li/Desktop/py/ceshi=======.py 
gong neng diao yong1
lalalal gong neng2
func1
li@li-ThinkPad-T420s:~/Desktop/py$ 

??↑正好和程序驗證的執行順序是一致的。

def zsq(funcX):
    def function():
        print("zsq work")
        print("gong neng diao yong1")
        funcX()
    return function

def zsq1(funcY):
    print("zsq1 work")
    def function():
        print("lalalal gong neng2")
        funcY()
    return function

@zsq
@zsq1
def func1():
    print("func1")


def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")

func1()
#保存完整代碼一段



??↓考慮只有返回值的情況

def zsq1(funX):
    def zsq1in():
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in


def zsq2(funY):
    def zsq2in():
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in


@zsq1
@zsq2
def name():
    return "name"


print(name())
zsq1  zsq2  name  zsq2  zsq1



??↓既有代碼,又有返回值

def zsq1(funX):
    def zsq1in():
        print("this is zsq1in function")
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in

def zsq2(funY):
    def zsq2in():
        print("this is zsq2in function")
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in

@zsq1
@zsq2
def name():
    print("this is name function")
    return "name"

print(name())
this is zsq1in function
this is zsq2in function
this is name function
zsq1  zsq2  name  zsq2  zsq1

??↓三個裝飾器:

def zsq1(funX):
    def zsq1in():
        print("this is zsq1in function")
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in

def zsq2(funY):
    def zsq2in():
        print("this is zsq2in function")
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in


def zsq3(funZ):
    def zsq3in():
        print("this is zsq3in function")
        return "zsq3  " +funZ()+"  zsq3"
    return zsq3in

@zsq1
@zsq2
@zsq3
def name():
    print("this is name function")
    return "name"

print(name())
this is zsq1in function
this is zsq2in function
this is zsq3in function
this is name function
zsq1  zsq2  zsq3  name  zsq3  zsq2  zsq1
  • 2-3?裝飾器—裝飾器的執行時間

??↓注意看以下代碼,這里沒有調用任何函數

def zsq1(funX):
    print("this is zsq1 function")
    def zsq1in():
        print("this is zsq1in function")
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in

def zsq2(funY):
    print("this is zsq2 function")
    def zsq2in():
        print("this is zsq2in function")
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in


def zsq3(funZ):
    print("this is zsq3 function")
    def zsq3in():
        print("this is zsq3in function")
        return "zsq3  " +funZ()+"  zsq3"
    return zsq3in

@zsq1
@zsq2
@zsq3
def name():
    print("this is name function")
    return "name"


# print(name())
this is zsq3 function
this is zsq2 function
this is zsq1 function

??↑由此可知,裝飾在調用之前就已經開始了。

  • 2-4?裝飾器—重點強調

  • 2-5?裝飾器—對有參數、無參數函數進行裝飾

??↓對無參的函數裝飾

def zsq(fp):
    print("this is zsq function")
    def zsqin():
        print("this is zsqinner function")
        fp()
    return zsqin

@zsq
def f1():
    print("i am f1")

f1()
this is zsq function
this is zsqinner function
i am f1

??↓對有參的函數裝飾

def zsq(fp):
    print("this is zsq function")
    def zsqin():
        print("this is zsqinner function")
        fp(123)
    return zsqin

@zsq
def f1(num):
    print("i am "+str(num))

f1()
this is zsq function
this is zsqinner function
i am 123

??這樣做雖然程序上沒有錯誤,但是你卻改變了原本函數的功能,因為原來的函數要傳遞參數,但是你現在調用卻不用參數。而且把參數的值的傳遞直接寫死到了裝飾器函數的內函數,也是沒有多少使用意義的。
??所以,原函數有參數,裝飾器函數的內函數也一定要有參數。

def zsq(fp):
    print("this is zsq function")
    def zsqin(num):
        print("this is zsqinner function")
        fp(num)
    return zsqin

@zsq
def f1(num):
    print("i am "+str(num))

f1(1)

??↓加入變長參數的處理。

def zsq(fp):
    print("this is zsq function")
    def zsqin(*num):
        print("this is zsqinner function")
        fp(*num)
    return zsqin

@zsq
def f1(a,b,c):
    print("%d%d%d"%(a,b,c))
@zsq
def f2(a,b,c,d):
    print("%d%d%d%d"%(a,b,c,d))

f1(1,2,3)
f2(1,2,3,4)
this is zsq function
this is zsq function
this is zsqinner function
123
this is zsqinner function
1234
  • 2-6?裝飾器—對帶有返回值的函數進行裝飾

def zsq(fp):
    print("this is zsq function")
    def zsqin():
        print("this is zsqinner function")
        ret =fp()
        return ret
    return zsqin

@zsq
def f1():
    return "hahaha"
 
print(f1())
this is zsq function
this is zsqinner function
hahaha
  • 2-7?裝飾器—通用裝飾器

def zsq(fp):
    print("this is zsq function")
    def zsqin(*args,**kwargs):
        print("this is zsqinner function")
        ret =fp(*args,**kwargs)
        return ret
    return zsqin

@zsq
def f1():
    return "hahaha"
 
@zsq
def f2(a,b):
    print(a)
    print(b)

print(f1())

f2(1,2)
  • 2-8?裝飾器—帶有參數的裝飾器

??還要再定義一個函數把原來的閉包套起來。
??python發現@裝飾器有參數,先調用閉包的外層參數。

def zsq_arg(zhe):
    print("this is zsq_arg function")
    def zsq(fp):
        print("this is zsq function")
        def zsqin(*args,**kwargs):
            print("this is zsqinner function")
            ret =fp(*args,**kwargs)
            return ret
        return zsqin
    return zsq

@zsq_arg("zheshisha")
def f1():
    return "hahaha"

print(f1())
this is zsq_arg function
this is zsq function
this is zsqinner function
hahaha

??↑zsq_arg("zheshisha")相當于一次執行函數,這個函數有個返回值,你返回誰,python就@誰,我覺得這里可以在內部放多個裝飾器,然后根據傳入的參數來動態選擇用哪一個裝飾器。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。