python3基礎篇(七)——函數
前言:
1 閱讀這篇文章我能學到什麼?
這篇文章將爲你詳細介紹python3函數的用法,將會非常詳細。
——如果你覺得這是一篇不錯的文章,希望你能給一個小小的贊,感謝你的支持。
目錄
程序的函數概念就類似數學上的函數概念。按照一定語法結構能完成特定的功能的代碼段,函數可以具有輸入和輸出(嚴格來說函數必須具有輸出,沒有任何輸出的函數是沒有意義的,只是語法結構上滿足函數定義)。
1 定義函數
python3以def
關鍵字表示定義函數,隨後自定義一個函數名,需要注意函數名在作用域內不能同名(後面補一章講下python3的作用域吧,這裏不懂先不必糾結)。函數名之後()
內爲參數列表,參數列表可以爲空,它表示傳入函數的參數。最後不要忘了:
符號。另起一行的是可選的“函數說明字符串”,用於對函數簡要描述,可省略不寫。函數體必須比函數名至少縮進一個空格或table。與c/c++類似,函數可以搭配return
關鍵詞結束函數並返回一個值給調用方,當函數不需要返回值時可以返回None
,也可以省略None
,甚至可以省略return
。
語法結構:
def FunctionName(ParameterList):
"Information"
FunctionBody
代碼示例:
def Fun1(): #無參數函數
"函數描述信息" #這不是必須的,看個人喜好,我個人是不喜歡在這裏寫函數註釋
print("Fun1")
#無返回值,省略return
def Fun2(a): #單參數參數列表
print("Fun2: %d" % (a))
return None #無返回值,返回None相當於無返回值
def Fun3(a, b): #多個參數的參數列表
print("Fun3: %d" % (a + b))
return #無返回值
#可以看到上面三種函數返回值都是等同的,返回值都是None
print(Fun1())
print(Fun2(1))
print(Fun3(1, 2))
運行結果:
Fun1
None
Fun2: 1
None
Fun3: 3
None
上面主要介紹了函數的結構,下面嘗試寫一些功能性的函數。
找出一串數字中最大的數:
代碼示例:
def FindMaxNum(List):
i = 0
MaxNum = List[0] #放入列表第一個數
for i in range(len(List)):
if List[i] > MaxNum:
MaxNum = List[i]
return MaxNum
ListNum = [3, 2, 5, 0, 2]
print(FindMaxNum(ListNum))
運行結果:
5
上面這個函數實現了輸出列表最大元素的功能。結構上具有函數名、參數列表、函數主體,功能上獨立且具有封裝性,算是一個標準的功能函數了。
另外還需要提的一點,python3的函數可以分爲兩類, 內置函數 和 自定義函數 。內置函數即python3自帶的,由python3設計者們已經爲我們實現的功能函數,比如“print”和元組、列表、字典等最基本的操作函數。因爲這些函數是最常用的,由python3設計者們統一替我們實現好可以避免我們重複“造輪子”,另外這些函數的可靠性也非常有保證(出了Bug別多想了,一定是你自己的問題)。自定義函數包括我們自己實現的函數,也包括別人提供的庫函數,它們並不是python3語言原生自帶的。
2 函數調用
2.1 如何調用一個函數
在定義函數時,函數結構要求具有函數名(在作用域內名稱唯一),具有參數列表(可以爲空)。那麼在調用函數時,就像每個人都有名字一樣我們需要寫出被調函數的函數名,()
內需要相應給出被調函數實現功能時需要我們提供的參數,最後具有返回值的函數根據需要決定是否接收其返回值。
代碼示例:
print("Test") #函數調用,寫明要調用的函數名,傳遞的參數列表。未使用返回值
運行結果:
Test
2.2 函數參數與可變及不可變類型
2.2.1 可變及不可變類型
在介紹數據類型的時候我們說過,python3的變量是沒有類型的,它可以存儲任意類型的值,需要分類型的是被存儲的數據。下面我寫一段代碼詳細看一下:
代碼示例:
#小數字緩存池-5~256內的整數變量,值不同地址不同,單值相同時地址相同
Number = 1 #定義變量Number,給其賦值整數1
print(id(Number)) #輸出變量地址
Number = 2 #改變其值,變量的地址發生改變,這時的Number已不同於之前
print(id(Number))
print("------------------------")
#非緩存池內的數
Number = 0.1 #定義變量Number,給其賦值浮點數1.0
print(id(Number))
Number = 0.2 #改變變量的值,變量的地址也發生改變,變量已經不同於之前
print(id(Number))
print("------------------------")
運行結果:
140720811398816
140720811398848
------------------------
2445246815632
2445246366960
------------------------
從這段代碼我們可以看出,不同於c/c++的變量。在有指針概念的編程語言中,變量定義好後其就具有了確定的地址,不會因爲變量中存儲的值改變而引起變量地址的改變。而在python3中變量中的值改變可能會引起變量地址改變。我們把這種改變值能引起變量地址改變的數據類型稱爲 不可變類型 ,而改變值不會引起變量地址改變的數據類型稱爲 可變類型。這麼理解起來有些難懂。
通俗點說,當一個變量中存儲的是不可變類型對象時(比如數值型),改變這個變量的值會使得變量地址發生改變,可以理解爲這個變量已經不是“原來”的變量了,雖然同名。所以你想做到讓原變量的值改變是不行的,也即不能改變原變量的值。而可變類型比如列表,你改變其元素的值,存儲該列表變量仍然是原變量(地址不變)。
那麼python3中有哪些是可變或不可變類型呢?
代碼示例:
#數值類型是不可變類型
Number = 0
print(id(Number))
Number = 1
print(id(Number))
print("-----------------------------------")
#字符串類型是不可變類型
String = "a"
print(id(String))
String = "b"
print(id(String))
print("-----------------------------------")
#元組元素不能修改,所以談不上可變還是不可變
Tuple = (1, 2)
print(id(Tuple))
Tuple = (1, 3) #不過給元組變量賦值其他元組時地址會變
print(id(Tuple))
print("-----------------------------------")
#列表類型是可變類型
List = [1, 2]
print(id(List))
List = [1, 3] #給列表變量賦值其他列表時地址會變
print(id(List))
List[1] = 4 #改變列表中元素時,列表變量地址不會變
print(id(List))
print(List)
print("-----------------------------------")
#集合類型是可變類型
Set = {1, 2}
print(id(Set))
Set = {1, 3}
print(id(Set)) #給集合變量賦值其他集合時地址會變
Set.add(4) #改變集合中元素時,集合變量地址不會變
print(id(Set))
print(Set)
print("-----------------------------------")
#字典類型是可變類型
Direction = {1:"1", 2:"2"}
print(id(Direction))
Direction = {1:"1", 3:"3"} #給字典變量賦值其他字典時地址會變
print(id(Direction))
Direction[3] = 4 #改變字典中元素時,字典變量地址不會變
print(id(Direction))
print(Direction)
print("-----------------------------------")
運行結果:
140720178583168
140720178583200
-----------------------------------
2669025674736
2669025613808
-----------------------------------
2669025805504
2669026779712
-----------------------------------
2669025812736
2669027094464
2669027094464
[1, 4]
-----------------------------------
2669026087648
2669027131680
2669027131680
{1, 3, 4}
-----------------------------------
2669025758592
2669025758656
2669025758656
{1: '1', 3: 4}
-----------------------------------
從以上代碼示例可以總結出,列表、集合、字典類型屬於可變類型,改變其元素的值變量地址不會變。而其他類型改變值時,或變量被賦值其他類型時地址都將改變。
我們討論了半天可變和不可變類型,到底有啥實際運用呢?主要在函數的傳參時會涉及到這個問題,請繼續往下看。
2.2.2 可變及不可變類型函數參數
我們知道c/c++中函數參數傳遞,傳遞的只是值,改變函數內的變量值不會引起外部變量的值改變,除非將變量的指針傳遞進函數內。在python3裏,當參數傳遞進函數的是不可變類型時,改變函數內變量的值不會引起函數外變量值得改變。當傳遞的是可變類型時,在函數內修改,函數外變量的值也會改變,因爲它們實際是同一個變量。
代碼示例:
def ModifyNum(Num):
Num += 1 #在函數內修改變量值
print(Num)
Num = 0
ModifyNum(Num)
print(Num) #函數外值不改變
print("-----------------------------")
def ModifyString(String):
String += "1" #在函數內修改變量值
print(String)
String = "0"
ModifyString(String)
print(String) #函數外值不改變
print("-----------------------------")
def ModifyList(List):
List[0] = 1 #在函數內修改變量值
print(List)
List = [0]
ModifyList(List)
print(List) #函數外變量值也改變
print("-----------------------------")
def ModifySet(Set):
Set.add(1) #在函數內修改變量值
print(Set)
Set = {0}
ModifySet(Set)
print(Set) #函數外變量值也改變
print("-----------------------------")
def ModifyDirection(Direction):
Direction[0] = "1" #在函數內改變變量值
print(Direction)
Direction = {0:"0"}
ModifyDirection(Direction)
print(Direction) #函數外變量值也改變
print("-----------------------------")
運行結果:
1
0
-----------------------------
01
0
-----------------------------
[1]
[1]
-----------------------------
{0, 1}
{0, 1}
-----------------------------
{0: '1'}
{0: '1'}
-----------------------------
函數參數傳遞時,不可變類型在函數內修改不會改變函數外變量的值,而可變類型在函數內修改值,函數外變量值也會相應改變。調用函數了解特性這個很重要。
2.2.3 幾種函數參數
函數參數在python3裏可以分爲4類:
- 必選參數:調用時必須按照定義時的參數列表順序和數量寫,不能省略和亂序。
- 關鍵字參數:允許不按定義是的參數列表順序寫,但是必須寫明是傳遞給哪個參數。
- 可選參數:參數在函數定義時參數列表裏給出對應參數的默認值,調用時可以重新傳遞值也可省略,省略時傳遞的爲定義時的默認值。
- 不定長參數:允許函數調用時傳遞比定義時參數列表裏更多數量的參數,必須按照一定語法規則寫。(我們常用的print函數就是一個不定長參數函數。)
2.2.3.1 必選參數
必選參數調用時必須和函數定義時保持一致的順序和數量。
代碼示例:
def Fun1(Parameter1, Parameter2, Parameter3): #必選參數,調用時需要按照其順序額和數量
print(Parameter1)
print(Parameter2)
print(Parameter3)
Fun1(1, "2", (3,)) #依次傳入參數Number、String、Tuple
運行結果:
1
2
(3,)
2.2.3.2 關鍵字參數
若在函數調用時明確寫明是給哪個參數傳遞值,調用時就可以不按定義時的順序,這樣的參數我們稱爲關鍵字參數。哪個參數成爲關鍵字參數取決於調用時指明瞭哪些參數的傳值。
代碼示例:
def Fun1(Parameter1, Parameter2, Parameter3): #確定參數,調用時需要按照其順序額和數量
print(Parameter1)
print(Parameter2)
print(Parameter3)
Fun1(1, Parameter3 = (3,), Parameter2 = "2")
print("--------------------------------------------------------")
Fun1(1, "2", Parameter3 = (3,))
print("--------------------------------------------------------")
Fun1(Parameter2 = "2", Parameter1 = 1, Parameter3 = (3,))
#Fun1(1, Parameter2 = "2", (3,)) #error
#Fun1(Parameter1 = 1, "2", (3,)) #error
運行結果:
1
2
(3,)
--------------------------------------------------------
1
2
(3,)
--------------------------------------------------------
1
2
(3,)
使用關鍵字調用函數可以不按函數定義時的順序。 調用函數時你可以把所有參數都寫成關鍵字參數,也可以一部分按必選參數調用,一部分按關鍵字參數調用。注意 混用必選參數和關鍵字參數時,必選參數必須在前面且符合函數定義時的順序(一定是前幾個),從第一個被作爲關鍵字參數開始之後的所有參數都必須是關鍵字參數,必須按關鍵字參數形式調用。比如上面的例子,Parameter1
作爲確定參數,而Parameter2
被作爲關鍵字參數,那麼Parameter3
也就是關鍵字參數了。還需注意的是,函數調用時必選參數必須寫在關鍵字參數前面,不能再關鍵字參數後出現必選參數的形式。
代碼示例:
def Fun1(a, b, c):
print(a, b, c)
Fun1(1, 2, 3)
Fun1(a = 1, b = 2, c = 3)
Fun1(1, b = 2, c = 3)
Fun1(1, 2, c = 3)
#Fun1(a = 1, b = 2, 3) #error a參數以關鍵字參數形式,則b和c也必須是關鍵字參數形式
#Fun1(a = 1, 2, 3) #error a參數以關鍵字參數形式,則b和c也必須是關鍵字參數形式
#Fun1(1, b = 2, 3) #error b參數以關鍵字參數形式,則c也必須是關鍵字參數形式
#Fun1(c = 3, 1, 2) #error 雖然c參數是以關鍵字參數形式,不要求a和b也是關鍵字參數形式,但是必選參數必須寫在關鍵字參數前面
#以下同樣是錯的
#Fun1(3, a = 1, b = 2)
##Fun1(2, 3, a = 1)
#Fun1(3, 1, b = 2)
運行結果:
1 2 3
1 2 3
1 2 3
1 2 3
2.2.3.3 可選參數
在定義函數時,可以給參數賦值一個默認值。在函數調用時若未給帶默認值的參數傳遞值時,會使用這個默認值。
代碼示例:
def Fun1(Parameter1 = 1, Parameter2 = 2, Parameter3 = 3):
print(Parameter1, Parameter2, Parameter3)
Fun1() #所有參數使用默認值
Fun1(4) #給Parameter1傳遞值
Fun1(4, 5) #給Parameter1和Parameter2傳遞值
Fun1(4, 5, 6) #給Parameter1、Parameter2、Parameter3傳遞值
#Fun1(_, _, 6) #error python不能像lua那樣用_表示參數使用默認值
print("-----------------------------------------")
def Fun2(Parameter1, Parameter2 = 2): #有必選參數和
print(Parameter1, Parameter2)
Fun2(1)
print("-----------------------------------------")
#def Fun3(Parameter1 = 1, Parameter2): #error 之後必須也都是可選參數
x = 1 #函數外變量
def Fun4(Parameter1 = x): #給的是變量值
print(Parameter1)
x = 2 #修改x的值
Fun4() #的值是創建函數時變量的值,後續修改x的值不會改變可選參數的默認值
print("-----------------------------------------")
運行結果:
1 2 3
4 2 3
4 5 3
4 5 6
-----------------------------------------
1 2
-----------------------------------------
1
-----------------------------------------
定義時需要 注意 函數參數列表中可選參數之後也都必須是可選參數,不能在可選參數之後又定義必選參數。可選參數的默認值一般用常量,也可以用變量。變量的值運行中是可以變的,而可選參數只會在構建函數時取一次變量的值。python3的可選參數不能像Lua那樣使用_
符號,比如print(_, 1)
表示第一個參數使用默認值,第二個參數進行值傳遞。在pathon3中要對後面的可選參數傳遞值必須也對前面的可選參數給出具體的傳遞至,這點對腳本語言來說確實不夠方便啊。
2.2.3.3 不定長參數
我們熟悉的print
函數就是不定長參數的函數。如果要實現一個函數去計算n個數的和,可以有兩種方式。一種是將n個數以元組列表等形式傳遞進函數,另外一種就是使用不定長參數。python3中沒有指針的概念,如果在函數定義的參數列表中參數名前加上*
就表示這是不定長參數,不定長的參數會以元組形式傳入。如果加的是**
也是不定長參數,不同的是不定長參數以字典形式傳入。所以python3的不定長參數有兩種形式,元組或字典形式,取決於所用的*
號個數。 注意 不定長參數一般寫成函數參數列表的最後一個參數,因爲後面所有傳入的參數值都被當做不定長參數裏的值,如果不定長參數後面還定義了其他參數,調用時必須按關鍵字參數形式調用。 注意 不定長參數的最小參數個數可以是0,這是對應的元組是空元組,對應的字典是空字典。
代碼示例:
def add(Size, *Numbers): #傳入求和的個數和不定長的數據
Sum = 0
for i in range(Size):
Sum += Numbers[i]
print(Size)
print(Numbers) #不定長參數最後以元組形式傳入
return Sum
print(add(3, 1, 2, 3))
print("---------------------------------------")
def Fun1(*UnsetTuple):
print(UnsetTuple)
def Fun2(**UnsetDirection):
print(UnsetDirection)
Fun1() #參數個數爲0,可變參數爲空元組
Fun2() #參數個數爲0,可變參數爲空字典
Fun1(1, 2, 3)
Fun2(a=1, b=2, c=3) #這時候的字典鍵値不能寫數值型
print("---------------------------------------")
def Fun3(*UnsetTuple, Size): #不定長參數後還定義了其他參數
print(UnsetTuple)
Fun3(1, 2, Size = 2) #調用時Size參數必須以關鍵字參數形式調用
#Fun3(1, 2, 3) #error 會被認爲沒給Size參數賦值
#Fun3(Size = 2, 1, 2) #error 前面我們說過關鍵字參數之後的參數也必須以關鍵字參數形式賦值
print("---------------------------------------")
運行結果:
3
(1, 2, 3)
6
---------------------------------------
()
{}
(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}
---------------------------------------
(1, 2)
可變參數一般都寫作最後一個參數,否則是不太規範的寫法。例子add
函數的Size
參數實際在python3中是沒必要的,因爲元組或字典有有內置的函數可以得到元素個數。*
或**
號與函數名之間可以加入空格,這個看個人書寫習慣,我個人不推薦加空格,因爲這看上去像是求積或求冪。
2.2.3.4 強制必選參數
這是python v3.8版本新加的特性。它不是具體的帶值參數,而是對參數調用時的書寫規則進行一些強制限制。
2.2.3.4.1 /參數
函數定義時,參數列表中/
參數之前的參數在調用時必須以必選參數形式。/
符號本身並不接受值,它只是限制了函數調用時參數的書寫規則。參數傳值時會跳過/
號。
代碼示例:
def Fun(a, b, /, c, d):
print(a, b, c, d)
Fun(1, 2, 3, 4)
#Fun(a = 1, b = 2, 3, 4) #error 前面講過,關鍵詞參數之後的參數也必須按關鍵詞參數形式
#Fun(a = 1, b = 2, c = 3, d = 4) #error /號前面的參數必須以必選參數形式
Fun(1, 2, 3, d = 4) #/號之後的參數按正常的可以是必選參數也可以關鍵字參數等
Fun(1, 2, c = 3, d = 4)
運行結果:
1 2 3 4
1 2 3 4
1 2 3 4
2.2.3.4.2 *參數
與/
不同的是*
作用於其後的參數。*
之後的參數調用時必須以關鍵字參數形式。
代碼示例:
def Fun(a, b, *, c, d):
print(a, b, c, d)
Fun(1, 2, c = 3, d = 4)
#Fun(1, 2, 3, 4) #error *號之後的參數必須以關鍵字參數形式
#Fun(1, 2, 3, d = 4) #error
運行結果:
1 2 3 4
所以/
和*
參數共同作用的結果爲:
代碼示例:
def Fun1(a, b, /, c, d, *, e, f): #/號前必須是必選參數,*號後必須是關鍵字參數
print(a, b, c, d, e, f)
Fun1(1, 2, 3, d = 4, e = 5, f = 6) #a和b必須是必選參數,d和e可以是必選參數也可以是關鍵字參數,e和f必須是關鍵字參數
'''
def Fun2(a, b, *, c, d, /, e, f): #error 提示/符號無效
print(a, b, c, d, e, f)
'''
運行結果:
1 2 3 4 5 6
因爲/
的作用域是往前的,*
的作用域是往後的,它們要求的規則不同。在使用時/
必須在*
前,否則語法錯誤。
2.3 遞歸調用
遞歸是一種函數的嵌套調用,函數自己調用自己。可以是一個函數循環調用自己,也可以是多個函數之間互相調用但必須形成循環。
代碼示例:
def Fun1(n):
print(n)
if n > 0:
return Fun1(n - 1)
else:
return n
def Fun2(n):
print(n)
if n > 0:
return Fun3(n - 1)
else:
return n
def Fun3(n):
print(n)
if n > 0:
return Fun2(n - 0.5)
else:
return n
Fun1(5) #遞歸調用
print("------------------------")
Fun2(3) #遞歸調用
運行結果:
5
4
3
2
1
0
------------------------
3
2
1.5
0.5
0.0
python3是不支持尾調用的。
3 匿名函數
匿名函數與普通函數不同。
- 使用關鍵字
lambda
而不是def
定義。- 普通函數的函數體可以是一個語句也可以是多個語句的代碼塊,而匿名函數的函數主體只能是一個語句。
- 普通函數調用的結果取決於
return
關鍵字返回的值,而匿名函數的結果爲這一個語句的計算結果值。- 和普通函數一樣匿名函數也有自己的命名空間,也分內部和外部變量。
- 匿名函數沒有函數名,通過定義時賦值的變量進行調用。可以使用列表將多個匿名函數捆綁在一起,通過列表取訪問。
3.1 匿名函數的定義
匿名函數主要用途是將一些常用的表達式簡潔的寫成匿名函數形式。
代碼示例:
Sum = lambda a, b: a + b #a和b是匿名函數的參數,a+b是匿名函數的表達式,結果即爲表達式結果
Sum2 = Sum #別名
print(Sum(1, 2)) #調用結果是表達式結果
print(Sum2(1, 2))
print("-------------------------------------------------")
c = 3 #全局變量
d = 4
def Fun1(d):
print(c, d) #普通函數訪問全局變量,普通函數局部變量與全局同名時使用局部變量
Fun2 = lambda d: print(c, d) #匿名函數訪問全局變量, 匿名函數也有自己的命名空間,局部變量與全局變量同名時使用局部變量
Fun1(5)
Fun2(5)
print("-------------------------------------------------")
#Fun3 = lambda : e = 6 #error 匿名函數函數主體不能定義變量
MaxNum = lambda a, b: a if a > b else b #三元運算
print(MaxNum(1, 2))
print("-------------------------------------------------")
#Fun3 = lambda a, b: a - b; a + b; #error 匿名函數的函數主體只能有一個語句
Fun4 = lambda : print("Test")
print(Fun4()) #調用結果是print函數的返回值
print("-------------------------------------------------")
運行結果:
3
3
-------------------------------------------------
3 5
3 5
-------------------------------------------------
2
-------------------------------------------------
Test
None
-------------------------------------------------
一些地方說匿名函數不能訪問全局,實測是可以訪問的。要注意匿名函數的函數主體只能是一個語句,如果這個語句是表達式那匿名函數的結果就是表達式的結果,如果是調用其他函數,那結果是被調用函數的結果。匿名函數時通過定義時的賦值的變臉來訪問的,這個變量也可以是列表,下面展示一個列表內捆綁多個匿名函數。
代碼示例:
FunList = [lambda a, b: a + b, #列表中嵌套進多個匿名函數
lambda a, b: a - b,
lambda a, b: a * b,
lambda a, b: a / b,
]
print(FunList[0](1, 2)) #通過列表訪問匿名函數,單獨一個匿名函數是沒有函數名去訪問的
print(FunList[1](1, 2))
print(FunList[2](1, 2))
print(FunList[3](1, 2))
運行結果:
3
-1
2
0.5
匿名函數的主要作用就是將一些常用的表達式進行簡單的封裝,增加代碼的重用性,要實現複雜的邏輯請使用普通函數。
3.2 匿名函數的參數
前面我們已經介紹過普通函數的參數了,匿名函數的參數用法與其完全一致。
代碼示例:
Fun1 = lambda a, b, c: print(a, b, c) #必選參數
Fun2 = lambda a, b = 2, c = 3: print(a, b, c) #含可選參數
Fun3 = lambda *a: print(a) #變長參數(元組形式)
Fun4 = lambda **a: print(a) #變長參數(字典形式)
Fun1(1, 2, 3)
Fun2(1)
Fun1(a = 1, c = 3, b = 2) #關鍵字參數
Fun3(1, 2, 3)
Fun4(a = 1, b = 2)
代碼示例:
1 2 3
1 2 3
1 2 3
(1, 2, 3)
{'a': 1, 'b': 2}