1. 調用函數
Python內置了很多有用的函數,我們可以直接調用
要調用一個函數,需要知道函數名稱和參數,比如求絕對值的函數abs,只有一個參數。
https://docs.python.org/3/library/functions.html#abs
也可以在交互式命令行通過help(abs)查看abs函數的幫助信息
調用函數的時候,如果傳入的參數數量不對,會報TypeError的錯誤,並且Python會明確的告訴你:abs()有且僅有1個參數
如果傳入的參數數量是對的,但參數類型不能被接受也會報typeError的錯誤,並且給出錯誤信息
數據類型轉換:
函數名其實就是指向一個函數對象的引用,完全可以把函數名符給一個變量,相當於給這個函數起了一個別名
2. 定義函數
在Python中,定義一個函數要使用def語句,依次寫出函數名,括號,括號中的參數和冒號:然後在縮進塊中編寫函數體,函數返回值用return語句返回:
def my_abs(x):
if x>=0:
return x
else:
return -x
空函數:
如果定義一個什麼也不做的函數,可以用pass語句:
def nop():
pass
pass也可以當做佔位符來使用
參數檢查:
調用函數時,如果參數不對,Python解釋器會自動檢查出來,並拋出TypeError
但是如果參數類型不對,Python解釋器就無法幫我們檢查。需要我們自己在函數裏實現:
def my_abs(x):
ifnot isinstance(x,(int,float)):
raise TypeError('bad operand type')
ifx>=0:
return x
else:
return -x
Python的函數返回多值其實就是返回一個tuple. 在語法上,返回一個tuple可以省略括號,而多個變量可以同時接收一個tuple,按位置賦給對應的值.
Practice:
定義一個函數quadratic(a,b,c)接收3個參數返回一元二次方程:ax2 + bx + c = 0 的來兩個解
import math
def quadratic(a,b,c):
ifb**2-4*a*c<0:
return 'No answer'
else:
x1=((-b)+math.sqrt(b*b-4*a*c)/(2*a))
x2=((-b)-math.sqrt(b*b-4*a*c)/(2*a))
return x1,x2
while True:
a=input("a:")
b=input("b:")
c=input("c:")
print(a+"x2+"+b+"x+"+c+"=0\n",quadratic(int(a),int(b),int(c)))
3.函數的參數
位置參數:
計算X2的函數:
>>> defpower(x):
return x*x
>>> power(5)
25
對於power(x)函數,參數x就是一個位置參數,當我們調用power函數時,必須傳入有且僅有的一個參數x:
當我們想要計算x的多次冪次方時我們需要增加一個參數來實現
>>> defpower(x,n):
s=1
while n>0:
n=n-1
s=s*x
return s
>>> power(9,3)
729
>>> power(3,3)
27
>>>
修改後的power(x,n)有兩個參數:x和n,這兩個參數都是位置參數,調用函數時,傳入的兩個值按照位置順序依次賦給參數x和n
默認參數:
新的power(x, n)函數定義沒有問題,但是,舊的調用代碼失敗了,原因是我們增加了一個參數,導致舊的代碼因爲缺少一個參數而無法正常調用,這個時候,默認參數就排上用場了。由於我們經常計算x2,所以,完全可以把第二個參數n的默認值設定爲2:
>>> defpower(x,n=2):
s=1
while n>0:
n=n-1
s=s*x
return s
>>> power(6)
36
>>> power(6,2)
36
>>>
默認參數可以簡化參數的調用,設置默認參數時需要注意的幾點:
1. 必須參數在前,默認參數在後,否則Python的解釋器會報錯
2. 當函數有多個參數時,把變化大的參數放在前面,變化小的參數放在後面,變化小的參數可以作爲默認參數
使用默認參數最大的好處就是可以降低調用函數的難度,而一旦需要更復雜的調用時,又可以傳遞更多的參數來實現,無論是簡單調用還是複雜調用,函數只需要定義一個。
有多個默認參數時,調用的時候既可以按照順序提供部分默認參數,也可以不按順序。
定義默認參數時必須牢記一點:默認參數必須指向不變的對象
爲什麼要設計str、None這樣的不變對象呢?因爲不變對象一旦創建,對象內部的數據就不能修改,這樣就減少了由於修改數據導致的錯誤。此外,由於對象不變,多任務環境下同時讀取對象不需要加鎖,同時讀一點問題都沒有。我們在編寫程序時,如果可以設計一個不變對象,那就儘量設計成不變對象。
可變參數:
在Python函數中,還可以定義可變參數,即傳入的參數個數是可變的,可以是1,2,任意個或者0個。
定義可變參數和定義一個list或tuple參數相比,僅僅在參數前面加了一個*號。在函數內部,參數numbers接收到的是一個tuple,因此,函數代碼完全不變。但是,調用該函數時,可以傳入任意個參數,包括0個參數:
>>> defcalc(*numbers):
sum =0
for n in numbers:
sum = sum +n*n
return sum
>>>calc(1,2,3)
14
>>>calc(2,4,5,7,8)
158
>>>
關鍵字參數:
關鍵字參數有什麼用?它可以擴展函數的功能。比如,在person函數裏,我們保證能接收到name和age這兩個參數,但是,如果調用者願意提供更多的參數,我們也能收到。試想你正在做一個用戶註冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足註冊的需求。
關鍵字參數允許傳入0個或者任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝爲一個dict
>>> defperson(name, age, **kw):
print('name:',name, 'age:', 'other:', kw)
>>>person('Alice', 23)
name: Alice age: other:{}
>>>person('Johnny', 26, boyfirend='Timmy')
name: Johnny age:other: {'boyfirend': 'Timmy'}
>>>person('Timmy', 26, BF='Johnny', fans='baobao')
name: Timmy age: other:{'BF': 'Johnny', 'fans': 'baobao'}
>>>
命名關鍵字參數:
對於關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數,至於到底傳入了哪些,就需要在函數內部通過kw檢查。
如果要限制關鍵字參數的名字,就可以用命名關鍵字參數,例如只接收city和job作爲關鍵字參數:
>>> defperson(name, age, * , city, job):
print(name, age,city, job)
>>>person('Orao', 24, city='Xian', job='Engineer')
Orao 24 Xian Engineer
如果函數定義中已經有了一個可變參數,後面跟着的命名關鍵字參數就不再需要一個特殊分隔符*
參數組合:
在Python中定義函數,可以用必選參數,默認參數,可變參數,關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用,但參數定義的順序必須是:必選參數,默認參數,可變參數,命名關鍵字參數和關鍵字參數。
雖然可以組合多達5種參數,但不要同時使用太多的組合,否則函數接口的可理解性很差。
總結:
1. Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常複雜的參數
2. 默認參數一定要用不可變對象,如果是可變對象,程序運行時會有邏輯錯誤
3. 注意定義可變參數和關鍵字參數的語法:
*args 是可變參數,args接收的是一個tuple
**kw是關鍵字參數,kw接收的是一個dict
4. 調用函數時傳入可變參數和關鍵字參數的語法:
可變參數既可以直接傳入:func(1,2,3)又可以先組裝成list或tuple,再通過*args傳入func(*(1,2,3));
關鍵字參數既可以直接傳入:func(a=1,b=2) 又可以先組裝dict,再通過**kw傳入: func(**{'a':1, 'b':2})
5. 命名的關鍵字參數是爲了限制調用者可以傳入的參數名,同時提供默認值
6. 定義命名的關鍵字參數子啊沒有可變參數的情況下不要忘記寫分隔符*,否則定義的將是位置參數
Practice: 讓一個函數可以接收一個或者多個數並計算乘積:
>>> defproduct(*numbers):
num=1
for n in numbers:
num = num*n
return num
>>>product(4,5)
20
4. 遞歸函數
如果一個函數在內部調用自身本身,這個函數就是遞歸函數
>>> deffact(n):
if n==1:
return 1
returnn*fact(n-1)
>>> fact(5)
120
遞歸函數的優點就是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰
使用遞歸函數需要注意防止棧溢出。在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出
解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。
尾遞歸是指,在函數返回的時候,調用自身本身,並且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只佔用一個棧幀,不會出現棧溢出的情況。
Practice:漢諾塔的移動
編寫move(n,a,b,c)函數,它接收參數n,表示3個柱子A,B,C中第1個柱子A的盤子數量,然後打印出把所有盤子從A藉助B移動到C的方法:
>>> defmove(n,a,b,c):
if n==1:
print(a,'--->',c)
else:
move(n-1,a,c,b) //把n-1個盤從a杆移動到b杆
move(1,a,b,c) //把最大的那個盤從a移動到c
move(n-1,b,a,c) //把剩下的n-1個盤從b移動到c
>>>move(2,'A','B','C')
A ---> B
A ---> C
B ---> C
>>>move(3,'A','B','C')
A ---> C
A ---> B
C ---> B
A ---> C
B ---> A
B ---> C
A ---> C
>>>