函數的本質就是**一段有特定功能、可以重覆使用的代碼**,這段代碼已經被提前編寫好了,並且為其起一個好聽的名字。在後續編寫程式過程中,如果需要同樣的功能,直接通過起好的名字就可以調用這段代碼。 ...
目錄
函數基礎
除了可以直接使用的內置函數外,Python還支持自定義函數,即將一段有規律的、可重覆使用的代碼定義成函數,從而達到一次編寫、多次調用的目的。
函數的本質就是一段有特定功能、可以重覆使用的代碼,這段代碼已經被提前編寫好了,並且為其起一個好聽的名字。在後續編寫程式過程中,如果需要同樣的功能,直接通過起好的名字就可以調用這段代碼。
函數的作用
- 結構化編程對代碼的最基本的封裝,一般按照功能組織一段代碼;
- 封裝的目的為了復用,減少冗餘代碼;
- 代碼更加簡潔美觀、可讀易懂
函數的定義
Python函數:能完成一定的功能,由若幹語句組成的語句塊、函數名稱、參數列表構成,它是組織代碼的最小單元。
語法格式
def 函數名(參數1,參數2,參數3,...):
'''註釋'''
函數體
return 返回的值
需要註意的地方:
- 函數名後面必須加冒號;
- 函數名即標識符,命名規範:小寫字母,多個單詞用
_
間隔; - 如果函數體和def不在同一行,必須縮進,約定4個空格;
- 若沒有
return
,則隱式返回一個None
值; - 如果函數體body語句只有一行,或者可以簡寫為一行,則可以寫在def的同行。例如:
def myfunc(x,y,z): print(x+y+z)
函數的調用
如何調用
函數聲明好之後,就可以執行函數,執行函數也稱為調用函數,方式為func_name(args)
,例如:
myfunc(1,2,3)
調用規則
函數的使用,必須遵循原則:先定義,後調用。
若在前面調用了,後面再定義,是否會報錯呢?
測試一:
def bar():
print('from bar')
def foo():
print('from foo')
bar()
foo() # 正常
測試二:
def foo():
print('from foo')
bar()
def bar():
print('from bar')
foo() # 也正常
測試三:
def foo():
print('from foo')
bar()
foo()
def bar():
print('from bar')
# 報錯
NameError: name 'bar' is not defined
函數的返回值
函數返回值:
- 沒有返回值:預設返回
None
- 返回一個值:函數結束了且返回一個值
- 返回多個值:多個值之間用逗號隔開,接收的時候可以用一個變數接收(返回元組),也可以用等量的多個變數接收
什麼時候需要有返回值?
調用函數,經過一系列的操作,最後要拿到一個明確的結果,則必須要有返回值。
通常有參函數需要有返回值,輸入參數,經過計算,得到一個最終的結果。
什麼時候不需要有返回值?
調用函數,僅僅只是執行一系列的操作,最後不需要得到什麼結果,則無需有返回值。
通常無參函數不需要有返回值。
多個返回值
Python的函數支持返回多個值。返回多個值時,預設以tuple的方式返回。例如,下麵兩個函數的定義是完全等價的。
def f():
return 1,2
def f():
return (1,2)
丟棄返回值
# 不進行任何賦值,將丟棄所有返回值
f()
# 可以通過索引取得某個或某幾個返回值
a = f()[0]
b = f()[1]
# 使用雙下劃線__或更多下劃線___________
# 丟棄第二個返回值
a, __ = f()
函數調用時的*
和**
除了在def
定義函數時,參數中可以使用*
或**
收集參數,在函數調用的時候也可以使用*
或**
分別解包元組(列表或其它對象)、字典。一定要註意區分函數定義和函數調用時的*
、**
,它們的用法是不通用的。
例如,解包元組:
def f(a,b,c,d):
print(a)
print(b)
print(c)
print(d)
T=(1,2,3,4)
f(*T)
*
除了可以解包元組,還可以解包其它可迭代對象,例如列表。甚至是字典也能解包,只不過*
解包的字典得到的是key組成的參數列表,和value無關:
D=dict(a=11,b=22,c=33,d=44)
f(*D)
# 輸出:
a
b
c
d
而**
解包的字典則是key=value
組成的參數列表。以下是函數調用時使用**
進行解包,字典D中的key名稱必須和def中定義的參數名稱相同:
def f(a,b,c,d):
print(a)
print(b)
print(c)
print(d)
D=dict(a=11,b=22,c=33,d=44)
f(**D)
# 輸出:
11
22
33
44
在函數調用時,可以混合位置參數、關鍵字參數、*
解包參數、**
解包參數。用法非常的靈活:
def f(a,b,c,d):
print(a)
print(b)
print(c)
print(d)
f(*(1,2),**{'d':4,'c':3})
f(1,*(2,3),**{'d':4})
f(1,c=3,*(2,),**{'d':4})
f(1,*(2,3),d=4)
f(1,*(2,),c=3,**{'d':4})
# 結果如下
1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
上面調用函數時的效果都等同於f(1,2,3,4)
。
函數的參數
函數的參數其實也是變數,只不過這些變數是獨屬於函數的本地變數,函數外部無法訪問。在函數調用的時候,會將給定的值傳遞給函數的參數,這實際上是變數賦值的過程。例如:
def myfunc(x,y,z):
print(x,y,z)
myfunc(1,2,3)
形參和實參
形參即變數名,實參即變數值,函數調用時,將值綁定到變數名上,函數調用結束,解除綁定。
形參:定義函數時,小括弧中的參數,是用來接收參數用的,在函數內部作為變數使用。
實參:調用函數時,小括弧中的參數,是用來把數據傳遞到函數內部用的。
參數的傳遞
參數的傳遞可以分為按指針傳參、按位置傳參、按關鍵字key=value
方式傳參。
按指針傳參
Python中變數賦值、參數傳遞都是通過指針拷貝的方式進行的。都只是拷貝了源數據的一個地址,而不會拷貝記憶體中完整的數據對象副本。所以,如果在函數內部修改變數指向的數據對象,會影響函數外部的數據。
例如:
def f(x):
print(x+3)
a = 4
f(a)
# 輸出結果
7
按位置傳參
如果是多個參數,則按從左到右的順序進行參數變數的賦值:
def f(x, y, z):
print(x)
print(y)
print(z)
f(2, 3, 4)
# 輸出結果
2
3
4
調用f(2,3,4)的時候,會按照從左向右的位置方式對本地變數x、y、z賦值:x=2,y=3,z=4
。
按關鍵字key=value
方式傳參
Python還支持key=value
的方式設置函數調用時的參數,使用key=value
的方式賦值時,順序不重要。這種函數調用時的傳值方式稱為關鍵字傳值。
例如:
def f(x, y, z):
print(x)
print(y)
print(z)
f(x=3, y="haha", z=4)
# 輸出
3
haha
4
也可以打亂順序,輸出結果不變:
f(x=3, z=4, y="haha")
還可以將key=value
和位置傳參的方式進行混合:
f(3, "haha", z=4)
但混合按位置傳參方式的時候,位置參數必須在其它傳參方式的前面,不僅此處結合key=value
時如此,後文中位置參數結合其它方式傳參也都如此:位置參數必須在最前面。例如,下麵的傳參方式是錯的:
f(z=4, 3, "haha")
參數預設值
在def或lambda聲明函數的時候,可以通過var=default
的方式指定參數的預設值。
例如:
def f(x=3):
print(x)
f(4)
f("haha")
f()
# 輸出結果
4
haha
3
上面的f(4)
和f("haha")
都對函數f()
的本地變數x
進行了賦值。但是最後一個調用語句f()
未賦值,而是使用參數的預設值3
。
設置參數預設值時,如果函數有多個參數,則帶預設值參數後面必須放在最後面。例如:
# 正確
def f(x, y, z=4)
def f(x, y=1, z=4)
# 錯誤
def f(x, y=4, z)
只要為參數設置了預設值,那麼調用函數的時候,這個參數就是可選的,可有可無的,如果沒有,則採用預設值。
def f(x, y=2, z=4):
print(x)
print(y)
print(z)
# 不採用任何預設值
f(2, 3, 4)
# 採用z的預設值
f(2, 3)
# 採用y的預設值
# 此時z必須按key=value的方式傳值
f(2, z=5)
# y、z都採用預設值
f(2)
# 輸出結果
2
3
4
2
3
4
2
2
5
2
2
4
位置可變參數*
對於任意長度的參數,可以在def
聲明的函數中使用*
將各位置參數收集到一個元組中。
def f(*args):
print(args)
f(1, 2, 3, 4)
# 結果輸出:(1, 2, 3, 4)
上面調用f(1, 2, 3, 4)
的時候,將所有參數都收集到了一個名為args
的元組中。
既然是元組,就可以對參數進行迭代遍歷:
def f(*args):
for arg in args:
print(arg)
f(1,2,3,4)
# 輸出結果
1
2
3
4
必須註意,*
是按位置收集參數的。
def f(x, y, *args):
print(x)
print(y)
for arg in args:
print(arg)
f(1, 2, 3, 4)
按照從左向右的傳參規則,首先將1
賦值給x
,將2
賦值給y
,然後將剩餘所有的位置參數收集到args
元組中,所以args=(3,4)
。
如果*
後面還有參數,則調用函數的時候,後面的參數必須使用key=value
的方式傳遞,否則會收集到元組中,從而導致參數缺少的問題:
def f(x, *args, y):
print(x)
print(y)
for arg in args:
print(arg)
# 正確
f(1, 3, 4, y=2)
# 錯誤
f(1, 2, 3, 4)
上面調用f(1, 3, 4, y=2)
的時候,會按照位置參數對x
賦值為1
,然後將所有位置參數收集到元組args
中,因為y=2
是非位置參數傳值方式,所以args=(3, 4)
。
如果為上面的y設置預設值:
def f(x, *args, y=2)
那麼f(1, 2, 3, 4)
會將(2, 3, 4)
都收集到元組args
中,然後y
採用預設值2
。
關鍵字可變參數**
除了可以使用*
將位置參數收集到元組中,還可以使用**
將key=value
格式的參數收集到字典中。
例如:
def f(x, **args):
print(x)
print(args)
f(1, a=11, b=22, c=33, d=44)
既然是將參數收集到字典中,就可以使用字典類的工具操作這個字典。例如,遍歷字典。
在**
的後面不能出現任何其它類型的參數。例如,下麵的都是錯誤的def
定義方式:
def f(x, **args, y)
def f(x, **args, y=3)
def f(x, **args, *t)
只能將位置參數或者*
的收集放在**
的前面。
def f(x, y, **args)
def f(x, *args1, **args2)
keyword-only參數形式
keyword-only的參數傳值方式表示def
中如果使用了*
,那麼在調用函數時,它後面的參數必須只能使用關鍵字傳值。其實在前面的內容中已經出現過幾次與之相關的說明。
另外註意,*
才是keyword-only
開關,**
不是,雖然**
也有自己的一些語法限制:任意類型的參數定義都必須在**
之前,包括keyword-only
類型的參數。例如:
def f(a, *b, c):
print(a, b, c)
按照keyword-only
的規則,被*b
收集的位置參數不包括c
,這個c
必須只能使用關鍵字的方式傳值,否則就被當作位置參數被收集到元組b
中。
# 正確
f(1, 2, 3, c=4)
# 錯誤
f(1, 2, 3, 4)
# 錯誤
f(1, c=4, 2, 3)
其中最後一個錯誤和如何def的定義無關,而是函數調用時的語法錯誤,前面已經解釋過:位置參數必須放在最前面。
還可以直接使用*
而非*args
的方式,這表示不收集任何參數,但卻要求它後面的參數必須按照關鍵字傳值的方式。
def f(a, *, b, c):
print(a, b, c)
以下是正確和錯誤的調用方式示例:
# 正確
f(1, b=2, c=3)
f(1, c=3, b=2)
f(b=2, c=3, a=1)
# 錯誤
f(1, 2, 3)
f(1, 2, c=3)
f(1, b=2, 3)
不過,keyword-only
後面的參數可以使用參數預設值。
def f(a, *, b, c=3)
那麼c
是可選的,但如果給定,則必須按關鍵字方式傳值。