python3進階篇(一)——函數的深入理解
閱讀這篇文章我能學到什麼?
說到函數的你可能會想到函數的參數、返回值、函數地址等,但是python3的函數用法非常靈活,比如允許函數嵌套定義,允許函數作爲參數或變量傳遞,允許函數返回函數。這篇文章將爲你講解這些“靈活“的用法
——如果您覺得這是一篇不錯的博文,希望你能給一個小小的贊,感謝您的支持。
目錄
1 再看函數
在基礎篇我們已經詳細的講解了函數的定義和調用,這裏我們更深入的研究下。下面這個例子幫助你回憶函數的定義和調用。
代碼示例:
def Function():
print("Call Function")
return 0
print(Function())
運行結果:
Call Function
0
1.1 函數調用的兩種形式
函數調用按是否帶()
可以分爲兩種方式,帶括號時將會執行函數,括號內的實參會傳遞給形參,函數的返回值會給調用處。如果不帶括號,則調用出得到的將會是函數的地址,函數並不會執行。
代碼示例:
def Function():
print("Call Function")
return 0
#函數兩種調用形式
print(Function())
print("---------------------------------")
print(Function)
運行結果:
Call Function
0
---------------------------------
<function Function at 0x000002B5F0896280>
另外,你可以嘗試多運行幾次,可以看到函數地址是變動的。
2 函數賦值給變量,嘗試刪除函數
2.1 將函數賦值給變量
根據上面函數的兩種調用形式,將函數賦值給變量相應也有兩種可能,一種是變量拿到函數的返回值,一種是拿到函數地址。拿到返回值好理解,相當於拿到函數的執行結果,拿到地址有什麼用?變量拿到函數地址後,相當於該變量也指向了這個函數,也即可以通過這個變量去調用這個函數。這其實是c/c++中指針的概念。
代碼示例“
def Function():
print("Call Function")
return 0
Func1 = Function() #得到函數的返回值0
print(Func1)
print("--------------------------------------------------")
Func2 = Function #得到函數的地址
print(Func2)
print("--------------------------------------------------")
Func2() #通過變量去調用函數
運行結果:
Call Function
0
--------------------------------------------------
<function Function at 0x0000018682F66280>
--------------------------------------------------
Call Function
函數不帶()
傳遞給變量的是函數地址,變量擁有函數地址後加上()
就可調用函數執行。當然,變量存儲的函數地址可以繼續賦值給其他變量。更正確的理解是地址賦值給變量就是給對象(這裏是函數)創建了一個新的引用,可以通過此引用去訪問對象本身。(實際上python3一切傳遞的都是引用,後面補一張講引用)。
2.2 嘗試del函數
我們知道python3中有關鍵字del
可以將各類對象的引用刪除,注意刪除的是引用,而不是直接刪除對象本身,當一個對象沒有被任何引用指向時,垃圾回收機制會自動將其釋放。函數定義時的函數名也只是該函數的一個引用,同樣可以把這個引用刪除。del
函數實際是刪除了該函數的第一個引用。
def Function():
print("Call Function")
return 0
Func = Function #得到函數的地址
del Function
# Function() #error: NameError: name 'Function' is not defined
Func() #通過變量去調用函數
運行結果:
Call Function
函數名Function
是該函數創建時存在的第一個函數引用,通過傳遞可以給這個函數創建新的引用Func
,這時如果刪除引用Function
那麼引用Func
依舊有效,但是Function
被刪除後就無法繼續使用。總之,函數定義時函數名就是其第一個引用,我們可以爲函數創建多個引用。函數的引用也可以被刪除,即便它是第一個引用。
3 函數嵌套定義
3.1 嘗試嵌套定義函數
在python3中允許函數的嵌套定義,比如在函數Func1
裏可以定義函數Func2
,在函數Func2
裏可以定義函數Func3
。定義函數時爲了調用,但是要注意函數嵌套時的作用域。如果函數Func2
在函數Func1
裏被被定義,那麼只能在Func1
函數裏調用Func2
函數,外部不能直接調用(與全局函數最大的不同就是作用域)。
運行結果:
def Func1():
print("Call Func1")
def Func2():
print("Call Func2")
def Func3():
print("Call Func3")
Func3()
Func2()
#Func3() #error: NameError: name 'Func2' is not defined
Func1() #調用Func1,也就調用了Func2和Func3
#Func2() #error: NameError: name 'Func2' is not defined
#Func3() #error: NameError: name 'Func2' is not defined
代碼示例:
Call Func1
Call Func2
Call Func3
因爲Func2
函數內調用了Func3
函數,Func1
函數內調用了Func2
函數,所以當我們調用了Func1
函數時,實際上附帶調用了Func2
和Func3
函數。
3.2 通過引用調用嵌套定義的函數
函數內部嵌套定義的函數不能被函數外部直接調用,但是我們前面提過函數可以有多個引用,其實函數時通過引用去調用的。我們可以通過全局的引用,讓它直接指向內部嵌套定義的函數,就可以實現在函數外部去調用嵌套定義的函數了。
代碼示例:
Func = None
def Func1():
global Func #聲明全局變量
print("Call Func1")
def Func2():
print("Call Func2")
Func = Func2
Func2()
Func1() #調用Func1,也就調用了Func2和Func3
print("---------------------------------------------")
Func() #通過全局引用去調用內部嵌套定義的函數
運行結果:
Call Func1
Call Func2
---------------------------------------------
Call Fun2
4 函數返回函數
調用不帶()
的函數名得到的函數的地址,如果將其作爲函數的返回值,函數調用處將得到返回函數的地址。
代碼示例:
def Func1():
print("Call Func1")
def Func2():
print("Call Func2")
return Func2
Func = Func1() #調用Func1,也就調用了Func2和Func3
print("------------------------------------")
Func() #外部訪問函數內部定義的嵌套函數
運行結果:
Call Func1
------------------------------------
Call Func2
用過函數返回內部函數的地址,也能實現外部訪問內部函數。
函數嵌套定義和函數返回函數結合,在函數調用是會有“多括號”情況的產生,非常有意思。
代碼示例:
def FuncA(NumA):
print("Call FuncA", "Param:%d" % NumA)
def FuncB(NumB):
print("Call FuncB", "Param:%d" % NumB)
def FuncC(NumC):
print("Call FuncC", "Param:%d" % NumC)
return NumC #FuncC正常返回值
return FuncC #FuncB返回內層函數FuncC地址
return FuncB #FuncA返回內層函數FuncB地址
print(FuncA(1)(2)(3)) #三個括號分別屬於不同函數的參數列表
print("---------------------------------------")
print(((FuncA(1))(2))(3)) #加上括號表明其搭配順序
運行結果:
Call FuncA Param:1
Call FuncB Param:2
Call FuncC Param:3
3
---------------------------------------
Call FuncA Param:1
Call FuncB Param:2
Call FuncC Param:3
3
邏輯並不算複雜,我們來理一理。我們使用FuncA(1)
去調用函數並傳遞值1
,FuncA
裏雖然定義了FuncB
和FuncC
函數,但是並沒有被調用,所以他們還不會被執行。然後FuncA
調用結束並返回下一個函數FuncB
地址,函數地址
+()
就又構成函數調用,所以FuncA(1)(2)
到這裏已經開始調用函數FuncB
並給其傳值2
,同理FuncA(1)(2)(3)
會執行FuncC
也就不難理解了。執行的順序是按嵌套順序從外到內,參數列表()
也一一對應,能遞推執行下去的基礎是函數返回值返回了下一個執行函數的地址。
5 函數作爲參數傳遞
如果一個函數實現了一部分功能,另外一部分功能是暫時不確定的,我們可以將這部分不確定的功能單獨封裝成一個函數,作爲參數傳遞進去供其調用。
def Function(Func):
print("Call Function")
Func()
def Func1():
print("Call Func1")
def Func2():
print("Call Func2")
Function(Func1) #將Func1函數的地址傳遞給Function函數
print("---------------------------")
Function(Func2) #將Func2函數的地址傳遞給Function函數
運行結果:
Call Function
Call Func1
---------------------------
Call Function
Call Func2
這裏的Function
函數功能有兩個:1、實現自己函數內的邏輯。2、調用外部傳入的函數,但外部函數執行的邏輯Function
是不關心的,由外部函數自己決定。python3裏有 裝飾器 的概念,其實就是函數作爲參數用法的演化,我將會在後面章節介紹python裝飾器。