02Python學(xué)習(xí)筆記之二.四【閉包、裝飾器】2019-08-17

章節(jié)號(hào) 內(nèi)容????????????
1圖片格式(png) 寬度大于620px,保持高寬比減低為620px
1-1 應(yīng)用
1-1-1 方法

第1章節(jié)? 閉包

  • 1-1?閉包—解釋

??↓函數(shù)引用:

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

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

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

??↑可以看到,如上的函數(shù),內(nèi)部的那個(gè)函數(shù)并未執(zhí)行。為什么呢?因?yàn)槊黠@只調(diào)用了test(),而沒有哪個(gè)地方調(diào)用了testin()。


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

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

??↑可以看到內(nèi)部函數(shù)已經(jīng)被調(diào)用了。

??關(guān)鍵點(diǎn)在于:

??里面的函數(shù)在調(diào)用時(shí),保存了外部函數(shù)傳入的參數(shù)。從某種程度上來(lái)說(shuō),調(diào)用內(nèi)部函數(shù)的時(shí)候,外部函數(shù)已經(jīng)執(zhí)行完畢了,這個(gè)參數(shù)應(yīng)該已經(jīng)度過(guò)了自己的生命周期,但是因?yàn)檫@是一個(gè)閉包,所以這個(gè)參數(shù)還能被內(nèi)部函數(shù)繼續(xù)使用。

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

??↑我們?yōu)閮?nèi)部函數(shù)再加上一個(gè)形式參數(shù),則調(diào)用時(shí)候也要作相應(yīng)改變。

1
3
2
10101

??小結(jié):外部函數(shù)的參數(shù)是基數(shù),內(nèi)部函數(shù)的參數(shù)是變數(shù)。
??特點(diǎn):外部函數(shù)的返回值,是內(nèi)部函數(shù)的一個(gè)引用。

  • 1-2?閉包—應(yīng)用

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

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

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

7

??↑來(lái)看調(diào)用后的答案。
??當(dāng)我第一次接觸閉包的應(yīng)用的時(shí)候,其實(shí)我內(nèi)心是拒絕的。我們仔細(xì)來(lái)看這個(gè)調(diào)用:

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

??大致,我覺得這句代碼就是,調(diào)用2次函數(shù),傳入3個(gè)參數(shù)。對(duì)不對(duì)?
??那么,這樣做從表面來(lái)看,是否是多此一舉呢?我們考慮用如下方式來(lái)達(dá)到相同目的:

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

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

??是不是這樣程序更清晰呢?
??但是,有這樣一個(gè)問題,讓我們從這方面來(lái)考慮:
??f(x)=a*x+b這個(gè)方程組,a和b值是常數(shù),即我們計(jì)算這個(gè)方程的時(shí)候,對(duì)于不同的x值,a和b只需要確定一次就行了,其余的時(shí)候我們只用改變x的值,就能求得不同的y值。
??考慮到這個(gè)問題,如果我們?cè)赼和b值一定的情況下,計(jì)算多個(gè)x值對(duì)應(yīng)的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))
#等等

??對(duì)了,你大概注意到了,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))

??是不是感覺,為了一個(gè)簡(jiǎn)潔,省力,真是無(wú)所不用其極呀!

第2章節(jié)?裝飾器

  • 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覆蓋了,所以執(zhí)行的是最后一個(gè)。
??下面開始需求提出及分析思路,假設(shè)類似多個(gè)函數(shù)如下:

def func1():
    print("func1")

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

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

??后因程序需要,每個(gè)函數(shù)需要加入一個(gè)功能N,則我們?nèi)绾谓鉀Q?顯而易見的方式為:

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

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

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

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

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

def function():
    功能N

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

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

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

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

??是不是感覺突然變得很美好?
??但是,編寫代碼應(yīng)該遵循的原則是,能擴(kuò)不改,老舊不動(dòng)。在函數(shù)里加了調(diào)用,某種意義上還是修改了代碼,可能造成不可描述的問題??紤]如下改動(dòng):

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

def func1():
    print("func1")

zsq(func1)

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

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


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

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

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

??↓完整的定義和調(diào)用如下:

#完整調(diào)用
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

??好的,剛才我們耗費(fèi)了大量的精力,完成了一種在不改變?cè)泻瘮?shù)名稱的情況下,改掉了函數(shù)調(diào)用內(nèi)容的方法,不可謂不精巧,不可謂不奇妙。但是直到現(xiàn)在,之前的需求還是沒有完全滿足,因?yàn)榈浆F(xiàn)在為止,這個(gè)方法還是需要顯式的調(diào)用func1=zsq(func1)一次,才能完成預(yù)想的任務(wù)。那么有很多個(gè)funcN的時(shí)候,不是一樣需要手動(dòng)調(diào)用嗎?
??↓別急,python已經(jīng)為我們做了隱藏細(xì)節(jié)的處理,剛才的代碼,我們只要稍加改動(dòng),就能完成預(yù)想的效果:

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

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

  • 2-2?裝飾器—2個(gè)裝飾器

??裝飾順序:最靠近函數(shù)的最先解析,最原理函數(shù)的最后解析。
??why?
??代碼解析,自上而下。當(dāng)解析到@zsq1時(shí),python就要準(zhǔn)備為下面的函數(shù)進(jìn)行裝飾了,但是下面是不是函數(shù)?不是!
??下面是另一個(gè)裝飾器@zsq2,所以python只能跳過(guò)@zsq1,繼續(xù)看@zsq2能不能執(zhí)行裝飾,即@zsq2下面緊跟的是函數(shù)還是其他的裝飾器,這里是函數(shù),所以就先解析@zsq2了。

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

print(name())

??↓以下假設(shè)調(diào)用的函數(shù)為func1 (),解析過(guò)程大致為:


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


??↑2、此時(shí)函數(shù)已經(jīng)自動(dòng)調(diào)用并跳轉(zhuǎn)至zsq1函數(shù)內(nèi),python自動(dòng)把func1的值賦給了funcY(這是裝飾器的工作機(jī)制),因此funcY指向print("func1")

??↑3、函數(shù)zsq1函數(shù)內(nèi)只有一條能執(zhí)行的語(yǔ)句return,執(zhí)行完return則zsq1函數(shù)執(zhí)行完畢。這相當(dāng)于python進(jìn)行的自動(dòng)調(diào)用zsq1完畢了,那么就要返回并執(zhí)行func1()

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

??↑5、當(dāng)系統(tǒng)想要開始執(zhí)行func1()的代碼時(shí),發(fā)現(xiàn)還有一個(gè)裝飾器@zsq,則繼續(xù)開始解析@zsq。python將自動(dòng)調(diào)用zsq函數(shù)。同時(shí)把當(dāng)前的func1的值傳遞給了funcX。

??↑6、這里馬上要執(zhí)行return語(yǔ)句返回。


??↑7、zsq函數(shù)返回后,func1的指向再次改變?yōu)閦sq函數(shù)的內(nèi)部函數(shù)為↓

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

??由上所知,funcX指向

        print("lalalal gong neng2")
        funcY()

??由上所知,funcY指向

         print("func1")

??那么我們做一個(gè)等式的替換,把上述3段代碼進(jìn)行融合:

        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$ 

??↑正好和程序驗(yàn)證的執(zhí)行順序是一致的。

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

??↓三個(gè)裝飾器:

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?裝飾器—裝飾器的執(zhí)行時(shí)間

??↓注意看以下代碼,這里沒有調(diào)用任何函數(shù)

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

??↑由此可知,裝飾在調(diào)用之前就已經(jīng)開始了。

  • 2-4?裝飾器—重點(diǎn)強(qiáng)調(diào)

  • 2-5?裝飾器—對(duì)有參數(shù)、無(wú)參數(shù)函數(shù)進(jìn)行裝飾

??↓對(duì)無(wú)參的函數(shù)裝飾

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

??↓對(duì)有參的函數(shù)裝飾

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

??這樣做雖然程序上沒有錯(cuò)誤,但是你卻改變了原本函數(shù)的功能,因?yàn)樵瓉?lái)的函數(shù)要傳遞參數(shù),但是你現(xiàn)在調(diào)用卻不用參數(shù)。而且把參數(shù)的值的傳遞直接寫死到了裝飾器函數(shù)的內(nèi)函數(shù),也是沒有多少使用意義的。
??所以,原函數(shù)有參數(shù),裝飾器函數(shù)的內(nèi)函數(shù)也一定要有參數(shù)。

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)

??↓加入變長(zhǎng)參數(shù)的處理。

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?裝飾器—對(duì)帶有返回值的函數(shù)進(jìn)行裝飾

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?裝飾器—帶有參數(shù)的裝飾器

??還要再定義一個(gè)函數(shù)把原來(lái)的閉包套起來(lái)。
??python發(fā)現(xiàn)@裝飾器有參數(shù),先調(diào)用閉包的外層參數(shù)。

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")相當(dāng)于一次執(zhí)行函數(shù),這個(gè)函數(shù)有個(gè)返回值,你返回誰(shuí),python就@誰(shuí),我覺得這里可以在內(nèi)部放多個(gè)裝飾器,然后根據(jù)傳入的參數(shù)來(lái)動(dòng)態(tài)選擇用哪一個(gè)裝飾器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容