章節號 | 內容???????????? |
---|---|
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就@誰
,我覺得這里可以在內部放多個裝飾器,然后根據傳入的參數來動態選擇用哪一個裝飾器。