函數
定義格式
"""
def 函數名 ():
# some codes
...
"""
# 求一個數的絕對值
def my_abs(x):
if x > 0:
return x
else:
return -x
空函數
# 定義一個什么事都不做的函數,pass僅是一個占位符,保證編譯通過
def fn():
pass
返回值
上面的示例中求絕對值的函數是返回的一個值,在Python中還可以返回多個值,但其實最終返回結果就是一個tuple。下面是一個求一元二次方程的函數
# 導入第三方模塊的API,相當于Java中的導包
import math
def test(a, b, c):
# b ** 2 表示b的平方 b ** 3 表示b的立方,依此在推
delta = b ** 2 - 4 * a * c
if delta > 0:
ret1 = (-b + math.sqrt(delta)) / (2 * a)
ret2 = (-b - math.sqrt(delta)) / (2 * a)
return ret1, ret2
else:
return "無解"
print(test(2, 3, 1)) # (-0.5, -1.0)
判斷參數類型
在上面的my_abs函數中,我們是對一個整數求絕對值,但若調用者傳遞過來的不是個整數怎么辦?所以我們需要對參數進行判斷。我們可以使用isinstance()函數來判斷
def my_abs(x):
# isinstance譯為是實例,加了一個not就表示不是實例(相當于java中的"!"符號)
if not isinstance(x, (int, float)):
# 當參數不合法時我們可以在方法拋出類型錯誤(相當于Java中的異常)
raise TypeError("arguments is not correct")
if x >= 0:
return x
else:
return -x
函數參數類型分類
-
位置參數
# 計算一個數的平方 def test(x): return x ** 2
x在test函數中就是一個位置參數,被調用時是必須要傳的。假如要計算一個數的立方,那么這個函數就不符合需求了,不過我們可以將計算幾次方讓用戶傳進來。
def test(x, n): return x ** n
-
默認參數
不過,我們經常計算一個數的平方,所以我們可以設置一個默認參數,若調用者沒有傳第二個參數,那就使用默認參數,若傳了,就使用調用者的傳過來的參數
def test(x, n=2): return x ** n
默認參數使用注意
def add_end(list=[]): list.append("end") return list # 第一次 print(add_end()) # ['end'] # 第二次 print(add_end()) # ['end', 'end']
這里有一個問題:默認參數是
[]
,但是函數似乎每次都“記住了”上次添加了'end'
后的list?第一次調用后,list的值由
[]
變為了'end'
,由于參數list是一個變量,如果改變了list的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]
了。所以,定義默認參數時必須指向不變對象
上面的代碼可以這樣寫
def add_end(list=None): if list is None: list = [] list.append("end") return list # 第一次 print(add_end()) # ['end'] # 第二次 print(add_end()) # ['end']
對于str、None這樣的不可變對象,當它們被創建時,對象的內部數據是不能被修改的。這樣做的好處就是減少了因修改數據導致的錯誤,并且,在多任務的環境下同時讀取對象不需要加鎖。所以,如果可以設計一個不變對象,那就盡量設計成不變對象。
-
可變參數
可變參數就是指參數的個數是可以變的。
# 比如給定一組數字a,b,c……,請計算a2 + b2 + c2 + ……。 def calc(*num): ret = 0 print(num) # (1, 2, 3, 4) for n in num: ret += n * n return ret print(calc(1, 2, 3, 4)) # 30 nums = [3, 4, 5, 6] # 假若傳的參數是list或tuple,我們可以這樣傳 print(calc(*nums))
當calc被調用時,雖然傳過來的是1,2,3,4,但是函數內部會先把它轉換成一個tuple,然后執行for循環
-
關鍵字參數
關鍵字參數用來對函數進行擴展
# 比如在注冊時,除了名字是必填外,年齡、性別是可選項 def register(name, **kw): print("name:", name, "other:", kw) register("Blain", sex="male", age=18) # name: Blain other: {'sex': 'male', 'age': 18} info = {"city": "HuNan", "job": "Engineer"} # 假若傳的參數是dict,我們可以這樣傳 register("Blain", **info) # name: Blain other: {'job': 'Engineer', 'city': 'HuNan'}
與可變參數類似,會先把傳過來的參數轉換成一個dict,然后執行下面的代碼
-
命名關鍵字參數
命名關鍵字參數用來限制關鍵字參數的名字
def register(name, *, city_name, job_name): print(name, city_name, job_name) register("Blain", city_name="SZ", job_name="Engineer") register("Blain", city="GZ", job_name="Salesman")
打印結果如下:
Traceback (most recent call last): Blain SZ Engineer File "...", line 91, in <module> register("Blain", city="GZ", job_name="Salesman") TypeError: register() got an unexpected keyword argument 'city'
很顯然,第二次調用時我們傳的是city,與命名參數不符,報一個TypeError的錯誤
如果函數的參數有一個可變參數時,后面的命名參數就不需要
*
了def register(name, age, *args, city, job): print(name, age, args, city, job)
-
參數組合
在Python函數中,參數的定義順序為:位置參數,默認參數,可變參數,命名關鍵字參數,關鍵字參數
def func1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def func2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw) func1(1, 2) # a = 1 b = 2 c = 0 args = () kw = {} func1(1, 2, c=3) # a = 1 b = 2 c = 3 args = () kw = {} func1(1, 2, 3, "a", "b") # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} func1(1, 2, 3, "a", "b", x=10) # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 10} func2(1, 2, d=15, y=15) # a = 1 b = 2 c = 0 d = 15 kw = {'y': 15}
總結
*args是可變參數,args接收的是一個tuple;
**kw是關鍵字參數,kw接收的是一個dict;
可變參數既可以直接傳入:
func(1, 2, 3)
,又可以先組裝list或tuple,再通過*args
傳入:func(*(1, 2, 3))
;關鍵字參數既可以直接傳入:
func(a=1, b=2)
,又可以先組裝dict,再通過**kw
傳入:func(**{'a': 1, 'b': 2})
;使用
*args
和**kw
是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。命名的關鍵字參數是為了限制調用者可以傳入的參數名,同時可以提供默認值。
定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符
*
,否則定義的將是位置參數。