本節(jié)摘要:函數(shù)的定義與調(diào)用;函數(shù)的參數(shù)退盯;位置參賽双絮;默認參數(shù);可變參數(shù)得问;關(guān)鍵字參數(shù)囤攀;命名關(guān)鍵字參數(shù);多種參數(shù)組合使用宫纬;遞歸函數(shù)焚挠;漢諾塔算法實現(xiàn)
Daily Record:每天一紀念,記錄下python的學習歷程漓骚,入門學習筆記與心得蝌衔。本學習筆記主要基于廖雪峰大大的Python教程。不積跬步蝌蹂,無以至千里~ .?(? ??_??)?
@[toc]
函數(shù)
函數(shù)的調(diào)用
調(diào)用函數(shù)噩斟,只需寫出函數(shù)名(輸入?yún)?shù))即可。
如孤个,求絕對值的函數(shù)abs
剃允,只有一個參數(shù)∑肜穑可通過Python官方網(wǎng)站查看文檔:
添加鏈接描述
或在交互式命令行通過help(abs)
查看abs
函數(shù)的幫助信息斥废。
>>> help(abs)
如圖在 help 界面時,想要回到原本書寫界面给郊,按
q
就可以了牡肉,也可以用Ctrl + z
统锤。
調(diào)用abs
函數(shù):
>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34
調(diào)用函數(shù)時,若輸入的參數(shù)數(shù)量有誤饲窿,會報錯TypeError
,且python會告知你唧席,abs()
有且僅有1個參數(shù),但你給出了兩個:
>>> abs(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)
若輸入的參數(shù)類型有誤嘲驾,也會報錯TypeError
,并且給出錯誤信息:str
是錯誤的參數(shù)類型:
>>> abs('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
max
函數(shù)max()
可以接收任意多個參數(shù)徒仓,返回最大值掉弛。
比較函數(shù)cmp(x, y)
需要兩個參數(shù)喂走,如果x<y芋肠,返回-1帖池,如果x==y睡汹,返回0囚巴,如果x>y文兢,返回1:
>>> cmp(1, 5)
-1
>>> cmp(5, 5)
0
>>> cmp(5, 2)
1
數(shù)據(jù)類型轉(zhuǎn)換
Python內(nèi)置的常用函數(shù)還包括數(shù)據(jù)類型轉(zhuǎn)換函數(shù)姆坚,例如:
int('14')
14
>>> int(3.14)
3
>>> float('3.14')
3.14
>>> str('3.14')
'3.14'
>>> str('88')
'88'
>>> unicode(100)
u'100'
>>> bool(0)
False
>>> bool(1)
True
bool是Boolean的縮寫兼呵,只有真(True)和假(False)兩種取值
bool函數(shù)只有一個參數(shù),并根據(jù)這個參數(shù)的值返回真或者假击喂。
1.當對數(shù)字使用bool函數(shù)時懂昂,0返回假(False),任何其他值都返回真凌彬。
>>> bool(0)
False
>>> bool(1)
True
2.當對字符串使用bool函數(shù)時铲敛,對于沒有值的字符串(也就是None或者空字符串)返回False伐蒋,否則返回True先鱼。
>>> bool('')
False
>>> bool(None)
False
>>> bool('good')
True
3.bool函數(shù)對于空的列表焙畔,字典和元祖返回False闹蒜,否則返回True绷落。
>>> a = []
>>> bool(a)
False
>>> a.append(1)
>>> bool(a)
True
4.用bool函數(shù)來判斷一個值是否已經(jīng)被設置筐喳。
>>> x = raw_input('Please enter a number :')
Please enter a number :
>>> bool(x.strip())
False
>>> x = raw_input('Please enter a number :')
Please enter a number :6
>>> bool(x.strip())
True
函數(shù)名即指向一個函數(shù)對象的引用避归,可以把函數(shù)名賦給一個變量梳毙,相當于給這個函數(shù)起了一個“別名”:
>>> a = max # 變量a指向max函數(shù)
>>> a(8, 2, 5) # 所以也可以通過a調(diào)用max函數(shù)
8
總結(jié):
調(diào)用函數(shù)账锹,需根據(jù)函數(shù)定義奸柬,輸入正確的參數(shù)廓奕。如果調(diào)用函數(shù)過程中出現(xiàn)錯誤桌粉,需要懂得看英文的錯誤信息番甩。
【練習】
請利用Python內(nèi)置的hex()
函數(shù)把一個整數(shù)轉(zhuǎn)換成十六進制表示的字符串:
# -*- coding: utf-8 -*-
n1 = 255
n2 = 1000
hex()
函數(shù)用于將10進制整數(shù)轉(zhuǎn)換成16進制缘薛,返回16進制數(shù)宴胧,以字符串形式表示恕齐。
【交作業(yè)】
n1 = 255
>>> hex(n1)
'0xff'
>>> print hex(n1)
0xff
>>> hex(n2)
'0x3e8'
>>> print hex(n2)
0x3e8
>>> print hex(n1), hex(n2)
0xff 0x3e8
函數(shù)的定義
函數(shù)定義格式:def 函數(shù)名(參數(shù)):
仪或,在縮進塊中編寫函數(shù)體范删,函數(shù)的返回值用return語句返回到旦。
例一:自定義一個求絕對值的my_abs函數(shù)
>>> def my_abs(x):
... if x >= 0:
... return x
... else:
... return -x
自行測試并調(diào)用my_abs
>>> print my_abs(-9)
9
返回結(jié)果正確添忘。
例二:定義一個求平方的函數(shù)
>>> def power(x):
... return x * x
...
>>> power(99) #函數(shù)的調(diào)用只需要寫出函數(shù)名( 輸入?yún)?shù))即可
9801
需要注意的是搁骑,==一旦執(zhí)行return語句靶病,則函數(shù)執(zhí)行完畢,并將結(jié)果返回煤辨。==
==如果沒有return
語句众辨,函數(shù)執(zhí)行完畢后也會返回結(jié)果鹃彻,只是結(jié)果為None
蛛株。return None
可以簡寫為return
谨履。==
例一:
def foo():
print 123
a = 321 # 無return其實是省略了return None
pirnt foo()
其實笋粟,這個函數(shù)定義相當于
def foo():
print 123
a = 321
return
pirnt foo()
運行得到結(jié)果
123
None
例二:
>>> def baobao():
... sx = 1
...
>>> print baobao()
None
例三:
>>> def baobao():
... if 1 == 2:
... return 1
... sx = 1
...
>>> print baobao()
None
例四:
>>> def baobao(isTrue):
... if isTrue:
... return 1
... sx = 1
...
>>> print baobao(True)
1
>>> print baobao(False)
None
例五:
def baobao(isBaobao):
... if isBaobao:
... return 1
... sx = 1
...
>>> print baobao(True)
1
>>> print baobao(False) # 分支判斷的時候,如果是False尝盼,就不往結(jié)構(gòu)里走了
==Python中东涡,每個函數(shù)疮跑,最終都有一個默認的return None祖娘。而且應該注意好return的位置渐苏,確定好其作用域仪吧,分析好最終return的結(jié)果薯鼠。==
在Python交互環(huán)境中定義函數(shù)時出皇,注意Python會出現(xiàn)...的提示郊艘。函數(shù)定義結(jié)束后需要按兩次回車重新回到>>>提示符下:
>>> def my_abs(x):
... if x >= 0:
... return x
... else:
... return -x
...
>>> my_abs(-9)
9
>>>
把my_abs()
的函數(shù)定義保存為abstest.py
文件,可以在該文件的當前目錄下啟動Python解釋器奈附,用from abstest import my_abs
來導入my_abs()
函數(shù)斥滤,注意abstest
是文件名(不含.py
擴展名):
>>> from abstest import my_abs
>>> my_abs(-9)
9
空函數(shù)
用pass
語句定義一個什么事也不做的空函數(shù)佑颇,pass
語句的作用是用來作為占位符挑胸,比如現(xiàn)在還沒想好怎么寫函數(shù)的代碼,就可以先放一個pass解藻,讓代碼能運行起來螟左。缺少了pass胶背,代碼運行就會有語法錯誤钳吟。
例三:定義一個空函數(shù)
>>> def nop():
... pass
...
>>> nop()
>>>
pass還可以用在其他語句里
if age >= 18:
pass
參數(shù)檢查
調(diào)用參數(shù)時评雌,
- 若參數(shù)數(shù)量錯誤,Python解釋器會自動檢查出來奔誓,并告訴你
TypeError
:
>>> my_abs(3, 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'my_abs' is not defined
- 若參數(shù)類型錯誤厨喂,Python解釋器就無法檢查。
>>> my_abs('A')
'A' #定義的my_abs沒有參數(shù)檢查斜纪,所以盒刚,這個函數(shù)定義不夠完善
>>> abs('A') #內(nèi)置函數(shù)abs會檢查出參數(shù)錯誤
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
修改一下my_abs
的定義因块,對參數(shù)類型做檢查,只允許整數(shù)和浮點數(shù)類型的參數(shù)吩愧。數(shù)據(jù)類型檢查可以用內(nèi)置函數(shù)isinstance
實現(xiàn):
>>> def my_abs(x):
... if not isinstance(x, (int, float)):
... raise TypeError('band operand type')
... if x >= 0:
... return x
... else:
... return -x
添加了參數(shù)檢查后耻警,如果傳入錯誤的參數(shù)類型腮恩,函數(shù)就可以顯示錯誤:
>>> my_abs('A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in my_abs
TypeError: band operand type
后續(xù)還會繼續(xù)學習錯誤和異常處理秸滴。
返回多個值
函數(shù)可以返回多個值
例如:游戲中經(jīng)常需要從一個點移動到另一個點,給出坐標释液、位移和角度误债,計算出新的坐標
import math # import math語句表示導入math包寝蹈,并允許后續(xù)代碼引用math包里的sin、cos等函數(shù)耍鬓。
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
獲得返回值:
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print x, y
151.961524227 70.0
但其實這只是一種假象界斜,Python函數(shù)返回的仍然是單一值:
>>> r = move(100, 100, 60, math.pi / 6)
>>> print r
(151.96152422706632, 70.0)
返回值是一個tuple。
在語法上峭判,返回一個tuple可以省略括號林螃,而多個變量可以同時接收一個tuple完残,按位置賦給對應的值谨设,所以扎拣,Python的函數(shù)返回多值其實就是返回一個tuple,但寫起來更方便刊愚。
總結(jié)
定義函數(shù)時百拓,需要確定函數(shù)名和參數(shù)個數(shù)决帖;
如果有必要地回,可以先對參數(shù)的數(shù)據(jù)類型做檢查畅买;
函數(shù)體內(nèi)部可以用return隨時返回函數(shù)結(jié)果谷羞;
函數(shù)執(zhí)行完畢也沒有return語句時湃缎,自動return None
函數(shù)可以同時返回多個值,但其實就是一個tuple蹂季。
【練習】
請定義一個函數(shù)quadratic(a, b, c)
偿洁,接收3個參數(shù)神凑,返回一元二次方程 的兩個解溉委。
提示:
一元二次方程的求根公式為:
計算平方根可以調(diào)用math.sqrt()
函數(shù):
>>> import math
>>> math.sqrt(2)
1.4142135623730951
【交作業(yè)】
>>> def quadratic(a, b, c):
... temp = (int, float)
... if isinstance(a, temp) and isinstance(b, temp) and isinstance(c, temp):
... delta = b ** 2 - 4 * a * c
... if delta >= 0:
... return (-b + math.sqrt(deta))/2/a, (-b - math.sqrt(deta))/2/a
... else:
... return (-b+cmath.sqrt(deta))/2/a,(-b-cmath.sqrt(deta))/2/a
... else:
... raise TypeError('bad operand type')
>>> ...
>>> quadratic(1, 2, 3)
((-1+1.4142135623730951j), (-1-1.4142135623730951j))
>>> quadratic(1, 6, 3)
(-0.5505102572168221, -5.449489742783178)
# -*- coding: utf-8 -*-
import math
def quadratic(a, b, c):
if not isinstance(a, (int, float)) and (b, (int, float)) and (c, (int, float)):
raise TypeError('bad operand type')
delta = b ** 2 - 4 * a * c
if delta < 0:
print '此方程無實數(shù)根'
return
x1, x2 = float('-inf'), float('-inf')
if delta == 0:
x1 = (-b/(2 * a))
x2 = x1
else:
x1 = (-b + math.sqrt(b ** 2 - 4 * a * c))/(2 * a)
x2 = (-b - math.sqrt(b ** 2 - 4 * a * c))/(2 * a)
return x1, x2
print 'quadratic(1, 2, 3) =', quadratic(1, 2, 3)
print 'quadratic(1, 6, 3) =', quadratic(1, 6, 3)
運行:
quadratic(1, 2, 3) = 此方程無實數(shù)根
None
quadratic(1, 6, 3) = (-0.5505102572168221, -5.449489742783178)
# -*- coding: utf-8 -i*-
import math
def quadratic(a, b, c):
if not isinstance(a, (int, float)) and (b, (int,, float)) and (c, (int, float))
raise TypeError('bad operand type')
delta = b ** 2 - 4 * a * c
if delta < 0:
return '此方程無實數(shù)根'
elif delta == 0:
x = -b / (2 * a)
return x
else:
x1 = (-b + math.sqrt(b ** 2 - 4 * a * c))/(2 * a)
x2 = (-b - math.sqrt(b ** 2 - 4 * a * c))/(2 * a)
return x1, x2
print 'quadratic(1, 2, 3) =', quadratic(1, 2, 3)
print 'quadratic(1, 6, 3) =', quadratic(1, 6, 3)
運行程序:
quadratic(1, 2, 3) = 此方程無實數(shù)根
quadratic(1, 6, 3) = (-0.5505102572168221, -5.449489742783178)
# -*- coding: utf-8 -i*-
import math
def quadratic(a, b, c):
if not isinstance(a, (int, float)) and (b, (int, float)) and (c, (int, float)): # 輸入數(shù)據(jù)合法性檢查
raise TypeError('bad operand type')
if a != 0:
delta = b ** 2 - 4 * a * c
if delta > 0:
x1 = (-b + math.sqrt(b ** 2 - 4 * a *c))/(2 * a)
x2 = (-b - math.sqrt(b ** 2 - 4 * a *c))/(2 * a)
print '二元一次方程有兩個根 x1 = %.2f, x2 = %.2f' % (x1, x2)
return x1, x2
elif delta == 0:
x = -b / (2 * a)
print '二元一次方程只有一個根 x = %.2f' % x
return x
else:
print '二元一次方程無實根'
return
else:
print '該方程不是一元二次方程'
a = int(input('請輸入 a = '))
b = int(input('請輸入 b = '))
c = int(input('請輸入 c = '))
quadratic(a, b, c)
print 與 return盡量不要混用藻三,一般只用return返回結(jié)果,print語句一般不要用逗概。
可以改為較好:
# -*- coding: utf-8 -i*-
import math
def quadratic(a, b, c):
if not isinstance(a, (int, float)) and (b, (int, float)) and (c, (int, float)): # 輸入數(shù)據(jù)合法性檢查
raise TypeError('bad operand type')
if a != 0:
delta = b ** 2 - 4 * a * c
if delta > 0:
x1 = (-b + math.sqrt(b ** 2 - 4 * a *c))/(2 * a)
x2 = (-b - math.sqrt(b ** 2 - 4 * a *c))/(2 * a)
return '二元一次方程有兩個根 x1 = %.2f, x2 = %.2f' % (x1, x2)
elif delta == 0:
x = -b / (2 * a)
return '二元一次方程只有一個根 x = %.2f' % x
else:
return '二元一次方程無實根'
else:
return '該方程不是一元二次方程'
a = input('請輸入 a = ')
b = input('請輸入 b = ')
c = input('請輸入 c = ')
print quadratic(a, b, c)
函數(shù)的參數(shù)
位置參數(shù)
計算的函數(shù):
def power(x):
return x * x
對于power(x)
函數(shù)枚钓,參數(shù)x
就是一個位置參數(shù)搀捷。
當我們調(diào)用power
函數(shù)時指煎,必須傳入有且僅有的一個參數(shù)x
:
>>> power(3)
9
>>> power(11)
121
若要計算威始,不可能定義無限多個函數(shù)晋渺,可以把
power(x)
修改為power(x, n)
木西,用來計算:
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
對于power(x, n)
函數(shù)八千,可以計算任意n次方。
修改后的power(x, n)
函數(shù)有兩個參數(shù):x
和n
沸停,這兩個參數(shù)都是位置參數(shù),調(diào)用函數(shù)時能颁,傳入的兩個值按照位置順序依次賦給參數(shù)x
和n
。
>>> power(3, 2) # 把3賦給x,把2賦給n绒怨,即計算3的2次方
9
>>> power(3, 3) # 把3賦給x纯赎,把3賦給n,即計算3的3次方
27
默認參數(shù)
接上面的例子南蹂,此時新的power(x, n)函數(shù)定義沒有問題犬金,但因為增加了新的參數(shù)會導致舊的代碼因為缺少一個參數(shù)而調(diào)用失敗:
>>> power(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() takes exactly 2 arguments (1 given)
Python的錯誤信息很明確:調(diào)用函數(shù)power()需要2個參數(shù)六剥,只給出了一個參數(shù)n。
當我們通過power函數(shù)來計算平方時疗疟,為了方便使用该默,我們可以通過設置默認參數(shù)來解決這一問題:
def power(x, n=2): #把n默認設置為2
s = 1
while n > 0:
n = n - 1
s = s * x
return s
>>> power(5) #在不輸入?yún)?shù)n的值時,默認n為2
25
>>> power(5, 2)
25
>>> power(5, 3) #對于n > 2的其他情況策彤,必須明確地傳入n
125
默認參數(shù)可以簡化函數(shù)的調(diào)用栓袖。設置默認參數(shù)時匣摘,有幾點要注意:
- 必選參數(shù)在前,默認參數(shù)在后裹刮,否則Python的解釋器會報錯
- 設置默認參數(shù)音榜,當函數(shù)有多個參數(shù)時,把變化大的參數(shù)放前面捧弃,變化小的參數(shù)放后面赠叼。變化小的參數(shù)就可以作為默認參數(shù)。
使用默認參數(shù)的好處:可以降低調(diào)用函數(shù)的難度违霞。
例如:寫一個學生注冊的函數(shù)嘴办,需要傳入name
和gender
兩個參數(shù)
def enroll(name, gender):
print 'name:', name
print 'gender:', gender
調(diào)用enroll()
函數(shù)只需要傳入兩個參數(shù):
>>> enroll('Bob', 'M')
name: Bob
gender: M
如果要繼續(xù)傳入其他信息如年齡、城市等葛家,會使得函數(shù)調(diào)用的復雜度增加户辞。
可以把年齡和城市設置為默認參數(shù):
def enroll(name, gender, age=6, city='Beijing'):
print 'name:', name
print 'gender:', gender
print 'age:', age
print 'city:', city
這樣學生注冊時,可以只提供兩個必須的參數(shù)癞谒,就不需要提供年齡和城市了:
>>> enroll('Bob', 'M')
name: Bob
gender: M
age: 6
city: Beijing
只有與默認參數(shù)不符的學生才需要提供額外的信息:
enroll('Alice', 'F', 7)
enroll('Amy', 'F', city='Tianjin')
總結(jié):
默認參數(shù)降低了函數(shù)調(diào)用的難度底燎,而一旦需要更復雜的調(diào)用時,又可以傳遞更多的參數(shù)來實現(xiàn)弹砚。
函數(shù)只需要定義一個双仍,就可以實現(xiàn)簡單調(diào)用和復雜調(diào)用。
有多個默認參數(shù)時桌吃,調(diào)用時朱沃,
- 可以按順序提供默認參數(shù),哪個參數(shù)位置上不提供茅诱,該參數(shù)就按默認參數(shù)使用默認值逗物。
- 也可以不按順序提供部分默認參數(shù)。需把參數(shù)名寫上瑟俭,例如調(diào)用
enroll('Amy', 'F', city='Tianjin')
翎卓,意思是,city
參數(shù)用傳進去的值摆寄,其他默認參數(shù)繼續(xù)使用默認值失暴。
默認參數(shù)的坑,例如:
先定義一個函數(shù)微饥,傳入一個list逗扒,添加一個END再返回:
def add_end(L=[]):
L.append('END')
return L
正常調(diào)用時,沒啥問題:
>>> add_end([1, 2, 3])
[1, 2, 3, 'End']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'End']
當使用默認參數(shù)調(diào)用時欠橘,一開始沒問題:
>>> add_end()
['End']
但矩肩,多次調(diào)用add_end()
時,就出錯誤了:
>>> add_end()
['End', 'End']
>>> add_end()
['End', 'End', 'End']
默認參數(shù)是[]
简软,函數(shù)似乎每次都“記住了”上次添加了'END'
后的list蛮拔。
原因解釋:
Python函數(shù)在定義的時候述暂,默認參數(shù)L
的值計算出為[]
,因為默認參數(shù)L
也是一個變量建炫,它指向?qū)ο?code>[]畦韭,每次調(diào)用該函數(shù),如果改變了L
的內(nèi)容肛跌,則下次調(diào)用時艺配,默認參數(shù)的內(nèi)容就變了,不再是函數(shù)定義時的[]
了衍慎。
== 定義默認參數(shù)要牢記一點:默認參數(shù)必須指向不變對象转唉!==
要修改上面的例子,我們可以用None
這個不變對象來實現(xiàn):
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
現(xiàn)在稳捆,無論調(diào)用多少次赠法,都不會有問題:
>>> add_end()
['End']
>>> add_end()
['End']
>>> add_end()
['End']
>>> add_end()
['End']
設計str
、None
這樣的不變對象的好處:不變對象一旦創(chuàng)建乔夯,對象內(nèi)部的數(shù)據(jù)就不能修改砖织,這樣就減少了由于修改數(shù)據(jù)導致的錯誤。此外末荐,由于對象不變侧纯,多任務環(huán)境下同時讀取對象不需要加鎖,同時讀一點問題都沒有甲脏。我們在編寫程序時眶熬,如果可以設計一個不變對象,那就盡量設計成不變對象块请。
可變參數(shù)
可變參數(shù)就是傳入的參數(shù)個數(shù)是可變的娜氏,可以傳入0個或任意個參數(shù)葛超,而這些參數(shù)在函數(shù)內(nèi)部組成一個list或tuple情组。當需要傳入x個參數(shù)但x不確定時弦撩,就需要用到可變參數(shù)再姑。如我們需要計算
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
調(diào)用的時候,需要先組裝出一個list或tuple歇由,傳入numbers,算出結(jié)果:
>>> calc([1, 2, 3])
14
>>> calc((4, 5, 6))
77
如果利用可變參數(shù),調(diào)用函數(shù)的方式可以簡化成這樣:
>>> calc([1, 2, 3])
14
>>> calc((4, 5, 6))
77
所以切省,把函數(shù)的參數(shù)改為可變參數(shù),定義可變參數(shù)和定義一個list或tuple參數(shù)相比帕胆,僅僅在參數(shù)前面加了一個*
號朝捆。:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
在函數(shù)內(nèi)部,參數(shù)numbers接收到的是一個tuple懒豹,因此芙盘,函數(shù)代碼完全不變驯用。但是,調(diào)用該函數(shù)時儒老,可以傳入任意個參數(shù)蝴乔,包括0個參數(shù):
>>> calc(1, 2)
5
>>> calc()
0
如果已經(jīng)有一個list或者tuple,要調(diào)用一個可變參數(shù)怎么辦驮樊?
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14
嫌繁瑣薇正,也可以簡化。Python允許你在list或tuple前面加一個*號囚衔,把list或tuple的元素變成可變參數(shù)傳進去挖腰。*nums
表示把nums
這個list的所有元素作為可變參數(shù)傳進去。這種寫法相當有用练湿,而且很常見猴仑。
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
可變參數(shù)允許你傳入0個或任意個參數(shù),這些可變參數(shù)在函數(shù)調(diào)用時自動組裝為一個tuple肥哎。
關(guān)鍵字參數(shù)
關(guān)鍵字參數(shù)允許你傳入0個或任意個含參數(shù)名的參數(shù)辽俗,這些關(guān)鍵字參數(shù)在函數(shù)內(nèi)部自動組裝為一個dict。
例如:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
>>> person('Michael', 30) #關(guān)鍵字參數(shù)是可選參數(shù)贤姆,這里可以只傳入必選參數(shù)
name: Michael age: 30 other: {}
>>> person('Bob', 35, city='Beijing') #傳入一個關(guān)鍵字參數(shù)
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer') #傳入兩個關(guān)鍵字參數(shù)
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
和可變參數(shù)類似榆苞,也可以先組裝出一個dict,然后霞捡,把該dict轉(zhuǎn)換為關(guān)鍵字參數(shù)傳進去:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
可以簡化成:
>>> person('Jack', 24, **extra) #當已經(jīng)有存在的dict時坐漏,可以**dict傳入
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra
表示把extra
這個dict的所有key-value用關(guān)鍵字參數(shù)傳入到函數(shù)的**kw
參數(shù),kw
將獲得一個dict碧信,注意kw
獲得的dict是extra
的一份拷貝赊琳,對kw
的改動不會影響到函數(shù)外的extra
。
命名關(guān)鍵字參數(shù) Python3.x
對于關(guān)鍵字參數(shù)砰碴,函數(shù)的調(diào)用者可以傳入任意不受限制的關(guān)鍵字參數(shù)躏筏。至于到底傳入了哪些,就需要在函數(shù)內(nèi)部通過kw
檢查呈枉。
仍以person()
函數(shù)為例趁尼,我們希望檢查是否有city
和job
參數(shù):
def person(name, age, **kw):
if 'city' in kw:
# 有city參數(shù)
pass
if 'job' in kw:
# 有job參數(shù)
pass
print('name:', name, 'age:', age, 'other:', kw)
但是調(diào)用者仍可以傳入不受限制的關(guān)鍵字參數(shù):
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制關(guān)鍵字參數(shù)的名字,就可以用命名關(guān)鍵字參數(shù)猖辫,方法具體如下:
接上例酥泞,例如,只接收city
和job
作為關(guān)鍵字參數(shù)啃憎。
def person(name, age, *, city, job):
print(name, age, city, job)
和關(guān)鍵字參數(shù)**kw
不同芝囤,命名關(guān)鍵字參數(shù)需要一個特殊分隔符*
,*
后面的參數(shù)被視為命名關(guān)鍵字參數(shù)。
調(diào)用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
如果函數(shù)定義中已經(jīng)有了一個可變參數(shù)悯姊,后面跟著的命名關(guān)鍵字參數(shù)就不再需要一個特殊分隔符*
了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
命名關(guān)鍵字參數(shù)必須傳入?yún)?shù)名羡藐,這和位置參數(shù)不同。如果沒有傳入?yún)?shù)名悯许,調(diào)用將報錯:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
由于調(diào)用時缺少參數(shù)名city
和job
仆嗦,Python解釋器把這4個參數(shù)均視為位置參數(shù),但person()
函數(shù)僅接受2個位置參數(shù)岸晦。
命名關(guān)鍵字參數(shù)可以有缺省值欧啤,從而簡化調(diào)用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
由于命名關(guān)鍵字參數(shù)city
具有默認值,調(diào)用時启上,可不傳入city
參數(shù):
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
使用命名關(guān)鍵字參數(shù)時邢隧,要特別注意,如果沒有可變參數(shù)冈在,就必須加一個*
作為特殊分隔符倒慧。如果缺少*
,Python解釋器將無法識別位置參數(shù)和命名關(guān)鍵字參數(shù):
def person(name, age, city, job):
# 缺少 *包券,city和job被視為位置參數(shù)
pass
參數(shù)組合——多種參數(shù)組合使用
在Python中定義函數(shù)纫谅,可以用必選參數(shù)、默認參數(shù)溅固、可變參數(shù)付秕、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù),這5種參數(shù)都可以組合使用侍郭。
但是注意询吴,參數(shù)定義的順序必須是:必選參數(shù)、默認參數(shù)亮元、可變參數(shù)猛计、命名關(guān)鍵字參數(shù)和關(guān)鍵字參數(shù)。
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函數(shù)調(diào)用的時候爆捞,Python解釋器自動按照參數(shù)位置和參數(shù)名把對應的參數(shù)傳進去奉瘤。
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
同樣的,在已用tuple或dict的情況下可以這樣調(diào)用
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
可見煮甥,對于任意函數(shù)盗温,都可以通過類似func(*args, **kw)
的形式調(diào)用它,無論它的參數(shù)是如何定義的成肘。
==注意: 雖然可以組合多達5種參數(shù)肌访,但不要同時使用太多的組合,否則函數(shù)接口的可理解性很差艇劫。==
總結(jié)
Python的函數(shù)具有非常靈活的參數(shù)形態(tài),既可以實現(xiàn)簡單的調(diào)用,又可以傳入非常復雜的參數(shù)店煞。
默認參數(shù)一定要用不可變對象蟹演,如果是可變對象,程序運行時會有邏輯錯誤顷蟀!
要注意定義可變參數(shù)和關(guān)鍵字參數(shù)的語法:
*args
是可變參數(shù)酒请,args接收的是一個tuple;
**kw
是關(guān)鍵字參數(shù)鸣个,kw接收的是一個dict羞反。
以及調(diào)用函數(shù)時如何傳入可變參數(shù)和關(guān)鍵字參數(shù)的語法:
可變參數(shù)既可以直接傳入:func(1, 2, 3)
,又可以先組裝list或tuple囤萤,再通過*args
傳入:func(*(1, 2, 3))
昼窗;
關(guān)鍵字參數(shù)既可以直接傳入:func(a=1, b=2)
,又可以先組裝dict涛舍,再通過**kw
傳入:func(**{'a': 1, 'b': 2})
澄惊。
使用*args
和**kw
是Python的習慣寫法,當然也可以用其他參數(shù)名富雅,但最好使用習慣用法掸驱。
命名的關(guān)鍵字參數(shù)是為了限制調(diào)用者可以傳入的參數(shù)名,同時可以提供默認值没佑。
定義命名的關(guān)鍵字參數(shù)在沒有可變參數(shù)的情況下不要忘了寫分隔符*毕贼,否則定義的將是位置參數(shù)。
【練習】
以下函數(shù)允許計算兩個數(shù)的乘積蛤奢,請稍加改造鬼癣,變成可接收一個或多個數(shù)并計算乘積:
# -*- coding: utf-8 -*-
def product(x, y):
return x * y
【交作業(yè)】
def product(x, *y):
for m in y:
x = x * m
return x
def product(*numbers):
if len(numbers) == 0:
raise TypeError
else:
s = 1
for n in numbers:
s = s * n
return s
遞歸函數(shù)
如果在函數(shù)內(nèi)部調(diào)用函數(shù)本身,則叫做遞歸函數(shù)远剩。遞歸的邏輯清晰扣溺,事實上所有的遞歸函數(shù)都可以用循環(huán)語句來實現(xiàn)。如構(gòu)造一個計算階乘的函數(shù):
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
根據(jù)函數(shù)定義看到計算過程如下:
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
遞歸函數(shù)的優(yōu)點是定義簡單瓜晤,邏輯清晰锥余。理論上,所有的遞歸函數(shù)都可以寫成循環(huán)的方式痢掠,但循環(huán)的邏輯不如遞歸清晰驱犹。
在使用遞歸函數(shù)時要防止棧溢出。
在計算機中足画,函數(shù)調(diào)用是通過棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的雄驹,每當進入一個函數(shù)調(diào)用,棧就會加一層棧幀淹辞,每當函數(shù)返回医舆,棧就會減一層棧幀。由于棧的大小不是無限的,所以蔬将,遞歸調(diào)用的次數(shù)過多爷速,會導致棧溢出。
解決遞歸調(diào)用棧溢出的方法是通過尾遞歸優(yōu)化霞怀,事實上尾遞歸和循環(huán)的效果是一樣的惫东,所以,把循環(huán)看成是一種特殊的尾遞歸函數(shù)也是可以的毙石。
尾遞歸是指廉沮,在函數(shù)返回的時候,調(diào)用自身本身徐矩,并且滞时,return語句不能包含表達式。這樣丧蘸,編譯器或者解釋器就可以把尾遞歸做優(yōu)化漂洋,使遞歸本身無論調(diào)用多少次,都只占用一個棧幀力喷,不會出現(xiàn)棧溢出的情況刽漂。
上面的fact(n)
函數(shù)由于return n * fact(n - 1)
引入了乘法表達式,所以就不是尾遞歸了弟孟。要改成尾遞歸方式贝咙,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數(shù)中:
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product) # return fact_iter(num - 1, num * product)僅返回遞歸函數(shù)本身拂募,num - 1和num * product在函數(shù)調(diào)用前就會被計算庭猩,不影響函數(shù)調(diào)用。
對比一下兩個函數(shù)的計算流程
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
優(yōu)化后的函數(shù)計算流程陈症,fact(5)
對應的fact_iter(5, 1)
的調(diào)用如下:
===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120
可見做了優(yōu)化之后棧不會增長了蔼水。因此,無論多少次調(diào)用也不會導致棧溢出录肯。
遺憾的是趴腋,大多數(shù)編程語言沒有針對尾遞歸做優(yōu)化,Python解釋器也沒有做優(yōu)化论咏,所以优炬,即使把上面的fact(n)函數(shù)改成尾遞歸方式,也會導致棧溢出厅贪。
總結(jié)
使用遞歸函數(shù)的優(yōu)點是邏輯簡單清晰蠢护,缺點是過深的調(diào)用會導致棧溢出。
針對尾遞歸優(yōu)化的語言可以通過尾遞歸防止棧溢出养涮。尾遞歸事實上和循環(huán)是等價的葵硕,沒有循環(huán)語句的編程語言只能通過尾遞歸實現(xiàn)循環(huán)眉抬。
Python標準的解釋器沒有針對尾遞歸做優(yōu)化,任何遞歸函數(shù)都存在棧溢出的問題贬芥。
漢諾塔算法實現(xiàn)
漢諾塔:漢諾塔(又稱河內(nèi)塔)問題是源于印度一個古老傳說的益智玩具吐辙。大梵天創(chuàng)造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞著64片黃金圓盤蘸劈。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。并且規(guī)定尊沸,在小圓盤上不能放大圓盤威沫,在三根柱子之間一次只能移動一個圓盤⊥葑ǎ——維基百科
簡單而言棒掠,漢諾塔的玩法是有三個柱子,我們分別假設為a屁商、b烟很、c,初始在a柱子上有由大到小堆放的圓盤蜡镶,要把a柱子上的圓盤權(quán)移動到c柱子上雾袱,并且規(guī)定大圓盤不能再小圓盤上面。漢諾塔的移動問題可以用遞歸函數(shù)簡單實現(xiàn)官还。
【練習】
請編寫move(n, a, b, c)函數(shù)芹橡,它接收參數(shù)n,表示3個柱子A望伦、B林说、C中第1個柱子A的盤子數(shù)量,然后打印出把所有盤子從A借助B移動到C的方法屯伞,例如:
# -*- coding: utf-8 -*-
def move(n, a, b, c):
if n == 1:
print(a, '-->', c)
【交作業(yè)】
算法分析(遞歸算法):
實現(xiàn)這個算法可以簡單分為三個步驟:
(1)把n-1個盤子由A 移到 B腿箩;
(2)把第n個盤子由 A移到 C;
(3)把n-1個盤子由B 移到 C劣摇;
從這里入手珠移,在加上上面數(shù)學問題解法的分析,我們不難發(fā)現(xiàn)饵撑,移到的步數(shù)必定為奇數(shù)步:
(1)中間的一步是把最大的一個盤子由A移到C上去剑梳;
(2)中間一步之上可以看成把A上n-1個盤子通過借助輔助塔(C塔)移到了B上,
(3)中間一步之下可以看成把B上n-1個盤子通過借助輔助塔(A塔)移到了C上滑潘;
# -*- coding: utf-8 -*-
def move(n, a, b, c):
if n == 1: #當只有一個盤子時垢乙,直接從a移到c
print(a, '-->', c)
else:
move(n-1, a, c, b) #把a柱子上的上面n-1個盤子(借助c)移到b
move(1, a, b, c) #把a柱子上的最底下一個盤子(借助b)移到c
move(n-1, b, a, c) #把之前移到b的n-1個盤子(借助a)移到c
>>>move(3, 'A', 'B', 'C') #當n=3時,結(jié)果如下
A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C
def move(n, a, b, c):
if n == 1: # 圓盤只有一個時语卤,只需將其從A塔移到C塔
print a, '-->', c # 將編b號為1的圓盤從A移到C
else:
move(n-1, a, c, b) # 遞歸追逮,把A塔上編號1~n-1的圓盤移到B上酪刀,以C為輔助塔
print a, '-->', c # 把A塔上編號為n的圓盤移到C上
move(n-1, b, a, c) # 遞歸,把B塔上編號1~n-1的圓盤移到C上钮孵,以A為輔助塔
move(3, 'A', 'B', 'C')
A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C
- 在這個原有的基礎上計算 移動次數(shù)s
返回本次移動的次數(shù)即可:
def move(n, a, b, c):
if n==1:
print(a,'-->',c)
return 1
else:
n1 = move(n-1,a,c,b)
print(a,'-->',c)
n2 = move(n-1,b,a,c)
return n1 + n2 + 1
total = move(6, 'A', 'B', 'C')
print(total)
- 詳解
假設A上面有n塊磚骂倘,借助C,把n-1塊磚放到B上巴席,然后把第N塊磚放到C上历涝,這算一輪;然后借助C漾唉,把n-2塊磚放到A上荧库,把第n-1塊放到C上,這是第二輪赵刑;這樣分衫,又回到了起點,相當于n-2了般此,如此往復就能全部搬運到C上蚪战。
代碼如下
def move(n,a,b,c):
if n == 1:
print(a,'-->',c)
else:
move(n-1,a,c,b)
print(a,'-->',c)
move(n-1,b,a,c)
下面具體分析一下這個代碼
當n=2時,很容易理解
move(1,a,c,b) ==》print(a,'-->',b) //a上的1放到b上
print(a,'-->',c) //a上的2放到c上
move(1,b,a,c) ==》print(b,'-->',c) //b上的1放到c上
當n=3時
move(2,a,c,b)
move(1,a,b,c) ==>print(a,'-->',c) //a上的1放到c上
print(a,'-->',b) **//a上的2放到b上
move(1,c,a,b) ==>print(c,'-->',b) //c上的1放到b上
print(a,'-->',c) **//a上的3放到c上
move(2,b,a,c)
move(1,b,c,a) ==>print(b,'-->',a) //b上的1放到a上
print(b,'-->',c) **//b上的2放到c上
move(1,a,b,c) ==>print(a,'-->',c) //a上的1放到c上
這個游戲主要是將磚塊借助C在AB上整體移動铐懊,然后每次都將最大的一塊放到C上邀桑,一直到最后。