版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
匯報(bào)人:WPSPython程序設(shè)計(jì)基礎(chǔ)第六章函數(shù)目錄01函數(shù)的定義和調(diào)用02函數(shù)的參數(shù)與返回值03函數(shù)的嵌套調(diào)用與變量的作用域04遞歸目錄05Python生態(tài)系統(tǒng)之time庫06小試牛刀07拓展實(shí)踐:利用遞歸繪制分形圖案08本章小結(jié)
理解函數(shù)的概念與意義。
掌握函數(shù)的定義與調(diào)用過程。
理解函數(shù)的參數(shù)與返回值。
理解變量的作用域。
理解遞歸思想。
了解
time庫的使用。在前面的學(xué)習(xí)中已經(jīng)接觸了很多
Python
內(nèi)置的函數(shù),如print()、input()、len()等。這些函數(shù)在需
要時(shí)即可隨時(shí)調(diào)用,為程序的編寫帶來很大的便利。但內(nèi)置的函數(shù)畢竟數(shù)量有限,而且主要面向通
用問題。在解決具體的實(shí)際問題時(shí),程序員能不能編制自己的函數(shù),從而進(jìn)一步利用函數(shù)的優(yōu)勢提
升程序的質(zhì)量呢?答案當(dāng)然是肯定的,本章就來介紹如何定義及使用函數(shù)。學(xué)習(xí)目標(biāo)PART16.1函數(shù)的定義與調(diào)用在第
1章介紹
turtle庫時(shí),代碼
1.8繪制了一個(gè)彩色的螺旋?,F(xiàn)將代碼
1.8
附在下方。當(dāng)時(shí)這段
代碼中的一些細(xì)節(jié)沒有正式介紹,現(xiàn)在回過頭來重讀這段代碼,對(duì)其中的所有細(xì)節(jié),尤其是列表與
模運(yùn)算的運(yùn)用,應(yīng)該都會(huì)比較清楚了。代碼
1.8
繪制彩色螺旋在第
1
章時(shí)就提到,左轉(zhuǎn)不同的度數(shù)代碼會(huì)得到不同的圖案。既然如此,可以將繪制圖案的一般邏輯提取出來,編寫為一段代碼,但不限定左轉(zhuǎn)度數(shù),而是等到后期具體繪制時(shí)再指定度數(shù),根據(jù)指定的度數(shù)不同,同一段代碼可以繪制出不同的圖案。6.1.1函數(shù)定義importturtlet=turtle.Turtle()t.speed(0)
#速度設(shè)為最快colors=
["red","yellow","green"]
#這是一個(gè)顏色盒forxin
range(20,100):#循環(huán)t.pencolor(colors[x%3])
#在
3個(gè)顏色中挑一個(gè)t.circle(x)
#以
x
為半徑畫個(gè)圓t.left(10)
#左轉(zhuǎn)
10°t.getscreen().exitonclick()這段代表繪制邏輯的代碼就是一個(gè)函數(shù),
如代碼
6.1所示。代碼
6.1
函數(shù)的定義。上述代碼中使用
def關(guān)鍵字定義了一個(gè)函數(shù),名稱為
draw_circles(t,degree),括號(hào)內(nèi)的兩個(gè)變量
是函數(shù)的參數(shù)。參數(shù)為函數(shù)工作提供了必要的數(shù)據(jù),如這里的函數(shù)draw_circles()要想工作的話,就必須提供一個(gè)繪圖的小海龜,以及繪制過程中左轉(zhuǎn)的度數(shù)兩個(gè)信息。有了這兩個(gè)信息,函數(shù)內(nèi)部的代碼就可以完成繪制工作了。6.1.1函數(shù)定義importturtle#定義函數(shù)defdraw_circles(t,degree):
#括號(hào)內(nèi)為參數(shù):t
為小海龜,degree
為左轉(zhuǎn)度數(shù)colors=
["red","yellow","green"]forxin
range(20,100):t.pencolor(colors[x%3])
#在
3個(gè)顏色中挑一個(gè)t.circle(x)t.left(degree)
#左轉(zhuǎn)相應(yīng)的度數(shù)artist=turtle.Turtle()artist.speed(0)#通過函數(shù)名調(diào)用函數(shù)draw_circles(t=artist,degree=120)#draw_circles(artist,130)
#簡略寫法artist.getscreen().exitonclick()那么什么時(shí)候?yàn)楹瘮?shù)提供這些參數(shù)的值呢?答案是調(diào)用的時(shí)候。代碼
6.1在創(chuàng)建了一個(gè)具體的小海龜后,通過函數(shù)名調(diào)用
draw_circles()函數(shù),并為兩個(gè)參數(shù)提供了值。在
參數(shù)傳值時(shí)可以明確指明參數(shù)與值的對(duì)應(yīng)關(guān)系,也可以如代碼中注釋所示的簡略寫法一樣,通過位
置來表明參數(shù)與值的對(duì)應(yīng)關(guān)系。下面總結(jié)一下函數(shù)的使用流程。在Python
中,函數(shù)要先定義后調(diào)用。函數(shù)的定義使用
def
關(guān)鍵字
來完成,def
之后是函數(shù)名,后續(xù)調(diào)用函數(shù)時(shí)就通過這個(gè)名稱來完成。函數(shù)名之后是括號(hào),其內(nèi)可以有
參數(shù),但參數(shù)不是必需的,不是所有的函數(shù)都需要參數(shù),如random()函數(shù)。def
所在行的行尾要有冒號(hào),
之后是縮進(jìn)的函數(shù)體,也就是實(shí)現(xiàn)函數(shù)功能的具體代碼。因此一個(gè)函數(shù)在定義時(shí)必須要寫明函數(shù)名、括號(hào)(即使沒有參數(shù),括號(hào)也要書寫)、函數(shù)體。如果需要參數(shù),要在括號(hào)內(nèi)指明,有的函數(shù)還會(huì)有
返回值。調(diào)用函數(shù)比較簡單,通過函數(shù)名即可,如果函數(shù)有參數(shù),則按要求傳遞參數(shù)的值。6.1.1函數(shù)調(diào)用有的讀者會(huì)覺得,直接修改代碼1.8中的左轉(zhuǎn)度數(shù)同樣可以達(dá)到繪制不同圖案的目標(biāo),為什么
要定義一個(gè)函數(shù),再在調(diào)用時(shí)傳遞不同的度數(shù)呢?所以學(xué)習(xí)函數(shù)的另外一個(gè)重要問題是要理解編程
中為什么要引入函數(shù),函數(shù)有什么意義、價(jià)值。一言以蔽之,函數(shù)可以實(shí)現(xiàn)代碼復(fù)用,讓程序結(jié)構(gòu)
更清晰,實(shí)現(xiàn)模塊化。為了更好地理解函數(shù)的價(jià)值,可以回顧一下第
3
章小試牛刀中的《少年中國說》案例。該案例
中有繪制長城的代碼,仔細(xì)觀察會(huì)發(fā)現(xiàn),無論是烽火臺(tái),還是城墻,都有一系列的垛口要繪制。而
繪制垛口的代碼邏輯是完全一致的,只是為了表現(xiàn)近大遠(yuǎn)小的透視效果,垛口的尺寸有所區(qū)別。既
然如此,可以將繪制垛口的代碼提取出來,定義為一個(gè)函數(shù),然后在需要繪制垛口的地方多次調(diào)用即可,如代碼
6.2所示。6.1.2函數(shù)的意義對(duì)于嵌套的列表,可以使用單層循環(huán)遍歷,也可以使用雙重循環(huán)遍歷,下面代碼5.5
演示了使
用雙重循環(huán)遍歷
mountains列表。代碼
6.2
函數(shù)的意義6.1.2函數(shù)的意義
(1)
(2)
代碼
6.2
的運(yùn)行結(jié)果與第
3
章中的并沒有區(qū)別,但程序會(huì)簡短一些。程序?qū)⒗L制一個(gè)小垛口需
要的小海龜前進(jìn)、轉(zhuǎn)向等一系列動(dòng)作定義為一個(gè)函數(shù)
draw_walls(size),其中的參數(shù)
size表示垛口的
寬度。這樣無論是繪制兩個(gè)烽火臺(tái),還是中間的一段城墻,都需要繪制一系列的垛口,只需根據(jù)尺寸大小調(diào)用這個(gè)函數(shù)即可。像繪制垛口這樣的相對(duì)獨(dú)立的任務(wù),而且有多次重復(fù)使用的需求,特別適合將其定義為一個(gè)函
數(shù),然后重復(fù)調(diào)用即可。因?yàn)橥瓿蛇@個(gè)工作的代碼只在函數(shù)內(nèi)寫了一遍,其他地方都是調(diào)用這個(gè)函
數(shù),將來代碼需要升級(jí)修改時(shí)也會(huì)比較方便。對(duì)于更加復(fù)雜的程序,如果將程序拆解為若干個(gè)功能,每個(gè)功能由一個(gè)或多個(gè)函數(shù)來完成,這
樣整個(gè)程序就變得模塊化了。將來某個(gè)功能的業(yè)務(wù)邏輯發(fā)生了變化,只需修改對(duì)應(yīng)的函數(shù)即可。而
且只要函數(shù)名和參數(shù)沒有變化,修改函數(shù)內(nèi)部代碼是不影響函數(shù)的調(diào)用的。這就像銀行自動(dòng)柜員機(jī)
外部界面沒有變化,只是更新了內(nèi)部點(diǎn)鈔模塊,這對(duì)自動(dòng)柜員機(jī)的使用沒有影響,無須通知使用柜員機(jī)的人。6.1.2函數(shù)的意義Python
中的函數(shù)一定是先定義后調(diào)用,不能調(diào)用一個(gè)不存在的函數(shù)。因此在代碼執(zhí)行的時(shí)間線
上,定義函數(shù)的代碼要發(fā)生在調(diào)用該函數(shù)的代碼之前。下面的代碼6.3
就弄反了函數(shù)定義與調(diào)用的
時(shí)間線先后關(guān)系,因此會(huì)報(bào)錯(cuò)。代碼
6.3
函數(shù)要先定義后調(diào)用6.1.3函數(shù)的調(diào)用importturtleimportmathturtle.setworldcoordinates(-1,-1,1,1)t=turtle.Turtle()t.speed(0)print("代碼根據(jù)輸入的自然數(shù)
n繪制花瓣")print("如果
n
為奇數(shù)則繪制
n個(gè)花瓣,如果
n
為偶數(shù)則繪制2n個(gè)花瓣")n_petal=int(input("請(qǐng)輸入自然數(shù)n:"))#對(duì)函數(shù)的調(diào)用發(fā)生在函數(shù)定義之前,會(huì)報(bào)錯(cuò)draw_flower(n_petal)
#NameError:name
'draw_flower'isnotdefineddefdraw_flower(n):delta=
2
*math.pi
/
500fori
in
range(501):theta=
i
*
deltar=math.sin(n
*theta)x=r
*math.cos(theta)y=r
*math.sin(theta)t.penup()t.goto(x,y)t.pendown()t.dot()上述代碼在調(diào)用
draw_flower()函數(shù)時(shí)報(bào)錯(cuò),提示錯(cuò)誤原因是
draw_flower()沒有定義。之所以發(fā)
生這種錯(cuò)誤,是因?yàn)?/p>
Python是解釋型語言,因此在調(diào)用
draw_flower()函數(shù)的代碼時(shí),Python解釋器完全不知曉定義該函數(shù)代碼的存在。只要調(diào)整函數(shù)定義與調(diào)用的順序,變?yōu)榇a6.4
所示的形式就沒有問題了。代碼6.4
繪制一定數(shù)量的花瓣6.1.3函數(shù)的調(diào)用defdraw_flower(n):#定義函數(shù)在前delta=
2
*math.pi
/
500#[0,2π]分成
500份fori
in
range(501):theta=
i
*
delta#theta從
0遞增到
2
πr=math.sin(n
*theta)#用來繪制花瓣的函數(shù)x=r
*math.cos(theta)#將極坐標(biāo)換算為直角坐標(biāo)y=r
*math.sin(theta)t.penup()t.goto(x,y)#定位到直角坐標(biāo)位置t.pendown()t.dot()#畫一個(gè)點(diǎn)print("代碼根據(jù)輸入的自然數(shù)
n繪制花瓣")print("如果
n
為奇數(shù)則繪制
n個(gè)花瓣,如果
n
為偶數(shù)則繪制2n個(gè)花瓣")
#調(diào)用
1:參數(shù)值保存在變量中n_petal=int(input("請(qǐng)輸入自然數(shù)n:"))draw_flower(n_petal)
#調(diào)用函數(shù)在后#調(diào)用
2:參數(shù)值直接用常數(shù)給出#draw_flower(5)importturtleimportmathturtle.setworldcoordinates(-1,-1,1,1)
t=turtle.Turtle()t.speed(0)輸入自然數(shù)
6后,代碼繪制的花瓣效果如圖
6.1所示。對(duì)于有參數(shù)的函數(shù),調(diào)用時(shí)要注意參數(shù)與傳遞的值的對(duì)應(yīng)。如果沒有明確指明哪個(gè)值是為哪個(gè)參數(shù)預(yù)備的,那么順序是很重要的,參數(shù)值會(huì)按照順序傳遞給對(duì)應(yīng)的參數(shù)。另外,傳遞給參數(shù)的值
可以是另外一個(gè)變量,如代碼6.4中的第一種調(diào)用形式;也可以是一個(gè)直接寫出的常數(shù)值,如代碼
6.4
中的第二種調(diào)用形式。如果是將變量的值傳遞給函數(shù)的參數(shù),二者的名稱是否相同并不重要,如
上述代碼將變量
n_petal
的值傳遞給函數(shù)參數(shù)
n,二者的名稱就不相同。6.1.3函數(shù)的調(diào)用圖
6.1n=6
時(shí)的花瓣效果圖Python
內(nèi)置的help()函數(shù)可以查看其他函數(shù)的幫助信息,如想了解print()函數(shù)的詳細(xì)信息,則可
以使用help(print)來查看其幫助信息。help()函數(shù)之所以能將print()函數(shù)的幫助信息顯示出來,前提當(dāng)
然是print()函數(shù)確實(shí)有幫助信息。那如何給出函數(shù)的幫助信息呢?很簡單,只需在函數(shù)體的開始位置給出描述函數(shù)功能的字符串即可,如代碼
6.5所示。代碼
6.5自定義函數(shù)的幫助信息一般函數(shù)的幫助信息字符串使用三引號(hào)給出,因?yàn)槿?hào)允許字符串換行,可以保留字符串中
的空白,這樣會(huì)比較美觀。有了這個(gè)幫助信息,即可使用內(nèi)置的help()函數(shù)來查看自定義的函數(shù)幫助了。6.1.4函數(shù)的幫助信息deftri_area(long,height):"""函數(shù)功能:已知底和高,計(jì)算三角形的面積。long:
三角形的底height:三角形的高"""area=
0.5
*
long
*heightprint("所求三角形的面積:",area)help(tri_area)
#使用
help查看函數(shù)說明tri_area(2,3)
#調(diào)用函數(shù)求三角形的面積代碼
6.5
的運(yùn)行結(jié)果如下。Helponfunctiontri_area
inmodule
main
:tri_area(long,height)函數(shù)功能:已知底和高,計(jì)算三角形的面積。long:
三角形的底height:
三角形的高所求三角形的面積:3.0PART26.2函數(shù)的參數(shù)與返回值1.參數(shù)的意義函數(shù)的本質(zhì)其實(shí)就是完成相對(duì)獨(dú)立功能的一段代碼,很多時(shí)候調(diào)用者在使用這段代碼時(shí)需要提
供一些必要的信息。例如,調(diào)用一個(gè)求長方形面積的函數(shù),需要將長方形的長和寬傳遞給該面積函
數(shù),否則該面積函數(shù)是無法工作的。也就是說,這些信息是函數(shù)正常工作的前提,在執(zhí)行函數(shù)體內(nèi)
的代碼時(shí),這些前提信息必須是已知的。可是在書寫函數(shù)定義時(shí),對(duì)函數(shù)的調(diào)用還沒有發(fā)生,自然
也沒有調(diào)用者傳遞這些前提信息給函數(shù)。那怎么辦呢?這就是函數(shù)參數(shù)的存在價(jià)值。參數(shù)是函數(shù)預(yù)備的變量容器,將來調(diào)用函數(shù)時(shí),參數(shù)用來接收調(diào)用者傳遞進(jìn)來的必要信息。而在書寫函數(shù)定義階段,雖然調(diào)用還沒有發(fā)生,這些參數(shù)變量還沒有具體的值,但可當(dāng)作參數(shù)的值都已具備,在這種情形下函數(shù)該如何工作,函數(shù)體內(nèi)的代碼就如何書寫即可。為了便于理解,可以把函數(shù)想象成一個(gè)封好了的盒子,如銀行的自動(dòng)柜員機(jī)。參數(shù)是函數(shù)的輸
入,相當(dāng)于柜員機(jī)的插卡口、觸摸屏等。調(diào)用函數(shù)時(shí)傳遞的必要信息好比使用柜員機(jī)時(shí)提供的銀行
卡、密碼等。有了這些必要信息后柜員機(jī)內(nèi)部就可以開始工作了,但這個(gè)工作細(xì)節(jié)對(duì)于柜員機(jī)外的
使用者來說是不可見的。同樣的,函數(shù)通過參數(shù)拿到必要的信息后,函數(shù)體就可以工作了。外部調(diào)
用者對(duì)函數(shù)體的細(xì)節(jié)也可以完全不知情,尤其對(duì)于較大型的程序,不同模塊的函數(shù)由不同的程序員
完成,函數(shù)的調(diào)用者只知函數(shù)名與參數(shù),不知函數(shù)體細(xì)節(jié)的情形是很正常的。6.2.1深入理解參數(shù)2.有默認(rèn)值的參數(shù)如果函數(shù)的某些參數(shù)在大多數(shù)情形下都會(huì)取某個(gè)值,不妨在定義函數(shù)時(shí)將其指定為參數(shù)的默認(rèn)
值。例如,Python
內(nèi)置的函數(shù)
print()有一個(gè)
end
參數(shù)用來控制輸出信息完成后使用什么字符結(jié)尾,
這個(gè)參數(shù)就將換行符設(shè)為默認(rèn)值。如果輸出信息后希望換行,那么在調(diào)用print()函數(shù)時(shí)就不必每次
都給
end參數(shù)傳值,這樣會(huì)方便很多。程序中自定義的函數(shù)也可以給參數(shù)設(shè)置默認(rèn)值,方法如代碼6.6
所示,在定義函數(shù)時(shí)直接為參
數(shù)指定的值就是其默認(rèn)值。這段代碼重構(gòu)了第
3章中背單詞的案例(參見代碼
3.15)。代碼將計(jì)算
每天單詞數(shù)的工作定義成一個(gè)函數(shù),完成計(jì)算所需的先決信息成為函數(shù)的參數(shù),如起始詞匯量、目標(biāo)詞匯量、實(shí)現(xiàn)目標(biāo)的期限等,其中期限的默認(rèn)值為
3
年。這意味著調(diào)用這個(gè)函數(shù)時(shí),必須為前兩個(gè)參數(shù)傳遞值,而第三個(gè)參數(shù)期限傳值與否均可。如果傳入了值則函數(shù)體內(nèi)的代碼會(huì)使用傳入的值,否則使用默認(rèn)值。注意觀察代碼最后對(duì)函數(shù)的兩次不同的調(diào)用及其運(yùn)行結(jié)果。6.2.1深入理解參數(shù)代碼
6.6
設(shè)置參數(shù)的默認(rèn)值6.2.1深入理解參數(shù)fortodayinrange(1,period+1):#內(nèi)層循環(huán)將嘗試每一天iftoday%
7
!=
0
and
today
%
30
!=
0:#不是復(fù)習(xí)日,背新詞
current+=words_each_dayifcurrent>=
target:
#目標(biāo)達(dá)成done=
Trueelse:#目標(biāo)未達(dá)成words_each_day+=1#每日新單詞的個(gè)數(shù)增加
1個(gè)msg=str(years)+
"年內(nèi)單詞量由"+
str(start)
+
"提到"
\+str(target)+",每天需背"
+
str(words_each_day)
+
"個(gè)新單詞"
print(msg)#使用不同的參數(shù)值調(diào)用函數(shù)how_many_words_per_day(3000,10000)how_many_words_per_day(5000,20000,2)defhow_many_words_per_day(start,target,years=3):#start:
起點(diǎn)#target:
目標(biāo)#years:
期限,默認(rèn)值為
3年period=years
*
365words_each_day=2#每天背新單詞的個(gè)數(shù)done=
False#成功標(biāo)記whilenotdone:#done
為
True時(shí)循環(huán)結(jié)束current=
start#將
current還原為
start值調(diào)用函數(shù)后的運(yùn)行結(jié)果如下。3
年內(nèi)單詞量由3000
提到
10000,每天需背
8個(gè)新單詞2
年內(nèi)單詞量由5000
提到
20000,每天需背
25個(gè)新單詞特別強(qiáng)調(diào),如果參數(shù)沒有默認(rèn)值,則調(diào)用函數(shù)時(shí)一定要為該參數(shù)傳遞值,否則會(huì)報(bào)錯(cuò)。另外要
注意,Python不允許無默認(rèn)值的參數(shù)出現(xiàn)在有默認(rèn)值參數(shù)之后,如若在代碼
6.6
中為參數(shù)
start設(shè)置
了默認(rèn)值,則后面所有的參數(shù)均要有默認(rèn)值。3.可接收多個(gè)值的參數(shù)Python有一個(gè)內(nèi)置的
sum()函數(shù)可以對(duì)多個(gè)數(shù)值進(jìn)行求和,但如果加數(shù)多于兩個(gè),一般要將加數(shù)
置于列表或元組中。否則,如果直接將
3個(gè)以上的數(shù)值交給
sum()函數(shù)求和,那么
sum()函數(shù)反而會(huì)
報(bào)錯(cuò),如代碼
6.7所示。代碼
6.7
Python
內(nèi)置的
sum()函數(shù)下面來定義一個(gè)自己的求和函數(shù),使其可以直接接收多個(gè)數(shù)值進(jìn)行求和,不必非要寫在序列容
器中。這個(gè)函數(shù)的內(nèi)部代碼比較簡單,就是多個(gè)數(shù)值進(jìn)行累加的問題。不好解決的是這個(gè)求和函數(shù)
應(yīng)該有幾個(gè)參數(shù)。如果定義其只有
2個(gè)參數(shù),則無法求
3個(gè)或更多加數(shù)的和,如果定義它有
4個(gè)參
數(shù),則無法求
5個(gè)以上的數(shù)之和。如此一來,好像定義該函數(shù)有多少個(gè)參數(shù)都不能很好地解決多個(gè)
數(shù)值求和問題。這時(shí)就需要一種特殊的參數(shù)了。6.2.1深入理解參數(shù)print(sum([1,4,5,2]))
#結(jié)果為
12print(sum((1,4,5),2))
#結(jié)果為
12print(sum(1,4,5,2))#直接將
4個(gè)加數(shù)傳給
sum
函數(shù),報(bào)錯(cuò)不妨給這個(gè)求和函數(shù)命名為
my_sum(),如代碼6.8
所示,注意其最后一個(gè)加了“*”的參數(shù),魔法就在于此。有了這個(gè)參數(shù),my_sum()函數(shù)就可以接收
多個(gè)數(shù)值進(jìn)行求和了。代碼
6.8
使用帶星號(hào)的參數(shù)上述代碼在最后調(diào)用
my_sum()函數(shù)時(shí),分別傳遞了
2、3、4個(gè)加數(shù),如果愿意,還可以傳遞更
多的加數(shù)。實(shí)現(xiàn)這一點(diǎn)的關(guān)鍵在于
my_sum()函數(shù)的第三個(gè)參數(shù),這個(gè)參數(shù)前面的“*”是一個(gè)記號(hào),
表明參數(shù)
args是一個(gè)元組。如果調(diào)用者向my_sum()函數(shù)傳遞兩個(gè)數(shù)值,則這兩個(gè)數(shù)值分別給參數(shù)
a
和
b,元組
args就是空的,這沒有問題;如果向
my_sum()函數(shù)傳遞多于兩個(gè)的數(shù)值,則前兩個(gè)數(shù)值
分別給
a
和
b,其余的都放到
args這個(gè)元組中,這樣也沒問題。通過這樣一個(gè)元組型的參數(shù)就解決
了
my_sum()函數(shù)接收的參數(shù)值個(gè)數(shù)不確定的問題。6.2.1深入理解參數(shù)defmy_sum(a,b,*args):result=
a
+bforn
in
args:#args其實(shí)是一個(gè)元組result
+=nprint(result)#輸出結(jié)果my_sum(1,4)#5my_sum(1,4,3)#8my_sum(1,4,3,7)#15my_sum()函數(shù)中的參數(shù)
a、b是按位置對(duì)應(yīng)傳值的,這類參數(shù)被稱為位置參數(shù),而有星號(hào)標(biāo)記的
參數(shù)是可變參數(shù),必須位于所有位置參數(shù)之后。為了可以更清楚地理解加星號(hào)參數(shù)的本質(zhì),來看下面的代碼
6.9。代碼
6.9
理解加星號(hào)參數(shù)的本質(zhì)代碼
6.9
的運(yùn)行結(jié)果如下。第一次調(diào)用
test()函數(shù)時(shí),由于只傳入了兩個(gè)數(shù)值,所以元組參數(shù)
args是空的;第二次調(diào)用
test()
函數(shù)時(shí),從第三個(gè)參數(shù)值
33開始都放到
args元組中了。6.2.1深入理解參數(shù)deftest(a,b,*args):print("參數(shù)
a:",a,"參數(shù)
b:",b)print("參數(shù)
args:",args)test(11,22)test(3,5,33,44,55,66)參數(shù)
a:11
參數(shù)
b:22參數(shù)
args:
()參數(shù)
a:3
參數(shù)
b:5參數(shù)
args:
(33,44,55,66)其實(shí)參數(shù)還可以在前面加兩個(gè)星號(hào),這樣的參數(shù)是一個(gè)字典,被稱為關(guān)鍵字參數(shù)。為關(guān)鍵字參
數(shù)傳值時(shí)要寫成鍵值對(duì)的形式,如代碼
6.10所示。代碼
6.10
使用帶兩個(gè)星號(hào)的參數(shù)代碼
6.10
的運(yùn)行結(jié)果如下。仔細(xì)觀察代碼及其運(yùn)行結(jié)果,可知函數(shù)定義中的
information參數(shù)是一個(gè)字典,調(diào)用函數(shù)時(shí)除第
一個(gè)位置參數(shù)外,后面類似性別="男"、朝代="唐"這樣的參數(shù)值均傳遞給字典
information。6.2.1深入理解參數(shù)defbuild_intro_msg(name,**information):intro_msg=name
+
":\n"forkey
in
information:msg=key
+
":"
+
information[key]
+
"
|
"intro_msg+=msgprint(intro_msg)build_intro_msg("李白",性別="男",朝代="唐",特長="寫詩")build_intro_msg("趙州橋",類別="拱橋",省份="河北省")李白:性別:男
|
朝代:唐
|
特長:寫詩
|趙州橋:類別:拱橋
|
省份:河北省
|前面例子中的
my_sum()函數(shù)在完成多個(gè)數(shù)值相加后,不通過
print()函數(shù)將結(jié)果輸出,而是使用
return語句將結(jié)果返回給調(diào)用者,由調(diào)用者決定如何處理這個(gè)結(jié)果。下面的代碼
6.11就是將
代碼
6.8稍作改動(dòng),將函數(shù)體代碼最后的print()函數(shù)修改為
return語句。my_sum()函數(shù)完成計(jì)算工作
后不再將結(jié)果直接輸出,而是返回給調(diào)用者,即計(jì)算結(jié)果會(huì)替換到代碼
6.11最后
3個(gè)
print()函數(shù)中
出現(xiàn)的
my_sum()函數(shù)處。代碼
6.11在函數(shù)中使用
return語句這樣一來,my_sum()函數(shù)只專注于完成該自己負(fù)責(zé)的核心計(jì)算過程,至于如何展示結(jié)果、如何
與用戶互動(dòng)等均由主程序完成。my_sum()函數(shù)和主程序代碼各司其職,分工明確,程序會(huì)變得更加
模塊化。6.2.2函數(shù)的返回值defmy_sum(a,b,*args):result=
a
+bforninargs:
#args其實(shí)是一個(gè)元組result
+=n#print(result)returnresult
#返回結(jié)果print("1+4=",my_sum(1,4))
#5print("1+4+3=",my_sum(1,4,3))
#8print("1+4+3+7=",my_sum(1,4,3,7))
#15代碼
6.11
的運(yùn)行結(jié)果如下,結(jié)果的展示比代碼
6.8更詳細(xì)了一些。1+4
=
51+4+3=
81+4+3+7=
15為了更好地體會(huì)這一點(diǎn),代碼
6.11還可以演變得更復(fù)雜一點(diǎn),成為代碼6.12所示的形式,
函數(shù)與主程序職責(zé)的劃分在這段代碼中體現(xiàn)得更加明顯。代碼
6.12
函數(shù)與主程序的職責(zé)劃分這段代碼將參與運(yùn)算的多個(gè)加數(shù)改為由鍵盤輸入,這就涉及加數(shù)由字符串改為數(shù)值型的數(shù)據(jù)類型轉(zhuǎn)換工作。6.2.2函數(shù)的返回值另外,為了最后的輸出效果更完備,代碼構(gòu)造了一個(gè)字符串
msg來描述加法運(yùn)算。所
有這些外圍工作都由主程序完成,而my_sum()函數(shù)仍然負(fù)責(zé)多個(gè)數(shù)值相加的核心計(jì)算工作。代碼6.12
的運(yùn)行結(jié)果如下。代碼
6.12不僅展示了主程序與函數(shù)之間功能的劃分,而且演示了如何將列表或元組傳遞給帶星
號(hào)的可變參數(shù)。注意觀察代碼的倒數(shù)第
2行,此時(shí)
numbers列表中保存的各加數(shù)已是數(shù)值型的。首
先將列表的前兩個(gè)元素分別傳遞給
my_sum()函數(shù)的前兩個(gè)位置參數(shù),而列表后續(xù)的元素都要傳遞給
加了星號(hào)的
args參數(shù)。但
numbers[2:]切片本身是一個(gè)列表,如果不進(jìn)行處理,則會(huì)把這個(gè)列表作為
一個(gè)整體傳遞給
args,這就不對(duì)了。畢竟
my_sum()函數(shù)是計(jì)算
a、b與
args
中的數(shù)值的加和,而不
是
a、b與列表的加和(也無法進(jìn)行這種運(yùn)算)。因此需要將number[2:]這個(gè)列表“打散”,將其中
的元素一個(gè)個(gè)傳遞給
args參數(shù),這就是numbers[2:]前面“*”的含義。6.2.2函數(shù)的返回值輸入所有加數(shù),用逗號(hào)分隔:3.5,5,7.2,9,1,33.5+5+7.2+9+1+3=28.7需要注意的是,函數(shù)體代碼一旦執(zhí)行到
return
語句,函數(shù)的執(zhí)行就到頭了,程序會(huì)立刻離開函
數(shù)體代碼,返回到調(diào)用函數(shù)的位置處繼續(xù)執(zhí)行后續(xù)代碼。所以如果函數(shù)體內(nèi)還有代碼跟在
return
的后面,則它們將沒有機(jī)會(huì)被執(zhí)行。下面看代碼6.13,函數(shù)體中最后的
print()函數(shù)沒有機(jī)會(huì)被執(zhí)行,
因?yàn)闊o論輸入的半徑值如何,函數(shù)的執(zhí)行都會(huì)遇到return語句,執(zhí)行完成后函數(shù)立刻返回。代碼
6.13
return語句意味著函數(shù)執(zhí)行結(jié)束綜上所述,函數(shù)可以使用
return
語句給調(diào)用者返回值。有返回值的函數(shù)是可以出現(xiàn)在賦值語句
右側(cè)的,等函數(shù)執(zhí)行完畢后函數(shù)的返回值就會(huì)“替換”在函數(shù)被調(diào)用的位置,也就是等號(hào)的右側(cè),
進(jìn)而賦給左側(cè)的變量。當(dāng)然,很多函數(shù)執(zhí)行完后不需要明確返回結(jié)果,這樣的函數(shù)沒有
return語句,
其返回值為None,如刪除列表元素的
del()函數(shù)、random庫中的
shuffle()函數(shù)等。6.2.2函數(shù)的返回值frommathimportpidefcircle_area(radius):if
radius>
0:returnpi
*radius
**
2else:return
0print('你能看到這句話嗎?
')#這行代碼不會(huì)被執(zhí)行print(f'半徑為
2
的圓面積:{circle_area(2)}')print(f'半徑為
0
的圓面積:{circle_area(0)}')代碼6.13的運(yùn)行結(jié)果如下。半徑為
2
的圓面積:12.566370614359172半徑為
0
的圓面積:0將函數(shù)帶不帶參數(shù)、有沒有明確的返回值兩個(gè)角度組合起來,可以把函數(shù)分成以下
4類。1.無參數(shù)無返回值型隨著物質(zhì)生活水平的富足,人們對(duì)高層次的精神文化的消費(fèi)需求與日俱增。閑暇時(shí)光,甚至是工作中忙里偷閑,去博物館看看展覽,與千百年前的文物對(duì)視,從深厚的歷史底蘊(yùn)中汲取力量,不失為現(xiàn)代人一種很好的精神享受。下面的代碼
6.14
模擬了一個(gè)定期推出十大熱門展覽的公眾號(hào),該函數(shù)不需查詢?nèi)溯斎肴魏螀?shù)信息,也沒有返回值,它只是輸出本期的十大熱門展覽信息。代碼
6.14
無參數(shù)無返回值的函數(shù)示例6.2.3四種函數(shù)類型defmuseum_exhibition_top10():print("本期十大熱搜展覽:")print("01.
故宮博物院:國子文脈——?dú)v代進(jìn)士文化藝術(shù)聯(lián)展")print("02.
南京博物院:大江萬古流——長江下游文明特展")print("03.
吳文化博物館:山水舟行遠(yuǎn)——江南的景觀")print("04.
上海自然博物館:“玉兔東升”展")print("05.陜西歷史博物館:玉韞九州——中國早期文明間的碰撞與聚合")print("06.蘇州灣博物館:吳韻江南——吳江歷史文化陳列展“舟車絲路”特展")print("07.中共一大紀(jì)念館:匠心筑夢——新蘇作的歷史記憶")print("08.天津博物館:再現(xiàn)高峰——館藏宋元時(shí)期文物精品特展")print("09.山西博物院:且聽鳳鳴——晉侯鳥尊的前世今生")print("10.吉林省博物院:永遠(yuǎn)的長安——陜西唐代文物精華展")museum_exhibition_top10()
#調(diào)用函數(shù)2.無參數(shù)有返回值型英文中有句諺語,“Oneappleaday,keepdoctoraway.”實(shí)際上,除了飲食健康,精神健康也很
重要。而中國的古典詩詞就是一個(gè)巨大的精神食糧寶庫,每日一句詩,必定對(duì)陶冶情操很有益處。
下面的代碼
6.15模擬了一個(gè)詩詞
App
的每日一詩功能,函數(shù)自帶一個(gè)詩詞庫,不需要使用者傳遞任
何參數(shù),每次運(yùn)行時(shí)都會(huì)隨機(jī)返回一句詩給使用者。代碼
6.15
無參數(shù)有返回值的函數(shù)示例6.2.3四種函數(shù)類型defone_poem_per_day():importrandompoems=
[("張九齡《望月懷遠(yuǎn)》","情人怨遙夜,竟夕起相思。"),("葉紹翁《游園不值》","春色滿園關(guān)不住,一枝紅杏出墻來。"),("杜牧《山行》","遠(yuǎn)上寒山石徑斜,白云深處有人家。"),("王勃《詠風(fēng)》","去來固無跡,動(dòng)息如有情。"),("白居易《對(duì)酒》","蝸牛角上爭何事?石火光中寄此身。")]peom=random.choice(poems)returnpeompoem_today=one_poem_per_day()width=len(poem_today[1])print(poem_today[1])print(f"{poem_today[0]:{chr(12288)}>{width}}")從左側(cè)代碼可知,有返回值的函數(shù)可以出現(xiàn)在賦值語句的右側(cè)。one_poem_per_day
函數(shù)返回的
詩句賦值給poem_today變量,因?yàn)楹瘮?shù)的返回值是一個(gè)元組,所以元組的第一個(gè)元素是詩人、作品
名,第二個(gè)元素才是摘錄的一句詩。主程序?yàn)榱孙@示得美觀些,使用了字符串格式化進(jìn)行對(duì)齊,其中的
chr(12288)表示全角空格。代碼
6.15
的運(yùn)行結(jié)果如下。從這個(gè)例子還可以看出,調(diào)用函數(shù)時(shí)不僅要知道函數(shù)名、所需參數(shù),如果有返回值,還要知道
其返回值是什么類型的數(shù)據(jù)。上述例子中,如果不知函數(shù)返回的是一個(gè)元組,不知元組內(nèi)元素的含義,那么是無法正確處理函數(shù)的返回值的。因此,函數(shù)名、參數(shù)、返回值等信息就像自動(dòng)柜員機(jī)的屏幕、插卡口、出鈔口,是正確使用函數(shù)所必須了解的。函數(shù)名、參數(shù)或返回值的數(shù)據(jù)類型如果發(fā)生了改動(dòng),則必須通知函數(shù)的使用者知曉,否則會(huì)影響使用者調(diào)用函數(shù)。相反,
如果函數(shù)名、參數(shù)、返回值的數(shù)據(jù)類型等均保持不變,僅僅是函數(shù)體內(nèi)的代碼發(fā)生變化,則使用者無須了解這種變化。
由此可見,函數(shù)名、參數(shù)和返回值等信息是函數(shù)調(diào)用者與函數(shù)之間達(dá)成的契約,一旦明確,則輕易不能修改,否則有可能使調(diào)用者無法正常調(diào)用函數(shù),導(dǎo)致程序出錯(cuò)。6.2.3四種函數(shù)類型蝸牛角上爭何事?石火光中寄此身。白居易《對(duì)酒》3.有參數(shù)無返回值型下面的代碼
6.16模擬了員工打卡環(huán)節(jié),函數(shù)需要傳入員工姓名,但沒有明確的返回值,只是在
函數(shù)體內(nèi)輸出一句歡迎信息。函數(shù)在調(diào)用時(shí)也不會(huì)用在賦值語句的右側(cè)。代碼
6.16
無參數(shù)無返回值的函數(shù)示例4.有參數(shù)有返回值型既有參數(shù)又有返回值的函數(shù)在
6.2.2
函數(shù)的返回值部分已有多個(gè)例子,這里不再贅述。6.2.3四種函數(shù)類型defclock_in(name):print(f'{name}打卡成功,預(yù)祝今天工作愉快!')clock_in("令狐沖")PART36.3函數(shù)的嵌套調(diào)用與變量的作用域函數(shù)的嵌套可以有兩種表現(xiàn)形式,一種是調(diào)用時(shí)的嵌套,表現(xiàn)為函數(shù)甲調(diào)用了函數(shù)乙,而函數(shù)
乙在執(zhí)行的過程中又調(diào)用了函數(shù)丙。另一種是函數(shù)乙在定義時(shí)寫在了函數(shù)甲的函數(shù)體中。這里主要
介紹第一種,函數(shù)調(diào)用時(shí)發(fā)生的嵌套。下面將以構(gòu)造0°~90°的三角函數(shù)表為例演示多個(gè)函數(shù)之間的嵌套調(diào)用。三角函數(shù)在很多科
學(xué)領(lǐng)域、工程技術(shù)問題中都有應(yīng)用,因此高精度的三角函數(shù)表在科學(xué)計(jì)算中是很重要的。在沒有現(xiàn)代
計(jì)算機(jī)之前,制作這樣一個(gè)數(shù)值表的工作量對(duì)任何人來說都是十分恐怖的。然而對(duì)數(shù)的發(fā)明者蘇格蘭數(shù)學(xué)家納皮爾(J.Napier)利用對(duì)數(shù)運(yùn)算,完全憑借人力制作了
0°~90°每隔
19的
8位三角函數(shù)表。納皮爾驚人的毅力值得世人贊嘆,而這毅力的背后一定有對(duì)自己所從事問題的癡迷作為支撐。6.3.1函數(shù)的嵌套使用納皮爾的精神值得我們學(xué)習(xí),納皮爾的工作量卻不會(huì)再壓在我們身上了。根據(jù)中學(xué)的三角函數(shù)
知識(shí),代碼
6.17可以構(gòu)建
0°~90°每隔19的正弦值表,并保留
8位小數(shù)。這段代碼以
19角的正弦
值為已知,根據(jù)同角正弦、余弦的關(guān)系計(jì)算得出19角的余弦值。然后按照下面的公式求后續(xù)每一個(gè)角的正弦值。仔細(xì)觀察上面的公式可以發(fā)現(xiàn),求下一個(gè)角的正弦值依賴上一個(gè)角的正弦值,這樣遞推,最后
歸結(jié)到19角的正弦值。這就是為什么代碼以19角正弦值為已知。代碼按照功能模塊進(jìn)行了切分,定
義了多個(gè)函數(shù),每個(gè)函數(shù)都有簡短的功能描述。當(dāng)然這里的重點(diǎn)不是其中的三角函數(shù)知識(shí),而是要厘清多個(gè)函數(shù)之間的調(diào)用關(guān)系。6.3.1函數(shù)的嵌套使用代碼
6.17
函數(shù)的嵌套調(diào)用:構(gòu)建正弦值表6.3.1函數(shù)的嵌套使用。defbuild_sin_table():"""構(gòu)建
0°~90°的正弦值表"""sin=
SIN_1fornin
range(2,5401):sin=
sin_next(sin)sin_list.append(round(sin,8))defsearch_sin_table(degree,minute):"""根據(jù)角的度數(shù)查詢正弦值degree:度minute:分返回值為-1
意味著輸入的角超過支持范圍"""idx=degree
*
60
+minuteif
idx<=
5400:returnsin_list[idx]else:(1)(2)構(gòu)造正弦值表的代碼由主程序和各功能函數(shù)構(gòu)成。在主程序中首先預(yù)備好
19角的正弦、余弦值,
其中求
19角的余弦值調(diào)用了輔助函數(shù)
2。之后預(yù)備好保存正弦值的列表,0°角、19角的正弦值已
經(jīng)直接添加到其中了。接下來是構(gòu)造正弦值表的核心環(huán)節(jié),由
build_sin_table()函數(shù)完成。這是一個(gè)
無參數(shù)無返回值函數(shù),它每計(jì)算得到一個(gè)角的正弦值,就直接將其添加到前面提到的列表中。因?yàn)橐瓿?/p>
90°以內(nèi)每隔19的角的正弦值,共有
5400
個(gè)角(不含
0°角),所以該函數(shù)內(nèi)部是一個(gè)循環(huán),循環(huán)內(nèi)調(diào)用了另外一個(gè)函數(shù)
sin_next(),即輔助函數(shù)
1。這個(gè)輔助函數(shù)
1實(shí)現(xiàn)了求取正弦值的遞推公式。但需要注意的是,輔助函數(shù)
1
還調(diào)用了輔助函數(shù)
2,而輔助函數(shù)
2
的書寫位置卻位于輔助函數(shù)
1之后,這違背了
Python
函數(shù)要先定義后調(diào)用的規(guī)則嗎?事實(shí)上運(yùn)行一下代碼就知道,程序是可以正常工作的。那這是為什么呢?原因就在于函數(shù)要先
定義后調(diào)用說的是在時(shí)間線上,而不是空間位置。代碼
6.17主程序前的許多行代碼僅僅是在定義各功能函數(shù),這些函數(shù)只是被Python
解釋器知曉,但沒有一個(gè)被執(zhí)行。只有到了主程序中調(diào)用build_sin_table()函數(shù)時(shí),才引發(fā)了各功能函數(shù)之間的嵌套調(diào)用,而這時(shí)所有的函數(shù)定義均已發(fā)生,不會(huì)出現(xiàn)函數(shù)未定義的錯(cuò)誤。所以雖然輔助函數(shù)1
調(diào)用了位于其后的輔助函數(shù)
2,但在調(diào)用發(fā)生時(shí)所有函數(shù)的定義均已完成,符合函數(shù)要先定義后調(diào)用的規(guī)則。6.3.1函數(shù)的嵌套使用。再回到代碼的主程序部分。構(gòu)造正弦值表完成后,主程序調(diào)用了查詢正弦值表的功能函數(shù)。這
是一個(gè)有參數(shù)、有返回值的函數(shù),函數(shù)把輸入的角度換算為分,根據(jù)角的分值去列表中提取相應(yīng)的
正弦值。這里需要注意兩個(gè)細(xì)節(jié),一是列表的
0
號(hào)元素為
0°角的正弦值,1
號(hào)元素為19角的正弦值;二是當(dāng)輸入的角度超過
90°時(shí)函數(shù)返回-1,這個(gè)特殊值是該函數(shù)和調(diào)用者之間的約定,當(dāng)調(diào)用者發(fā)現(xiàn)該函數(shù)返回-1
時(shí)即可知道提供的角度超出了限定范圍。這與字符串的find()方法沒有找到子字符串時(shí)返回-1是一樣的處理方式。其實(shí)程序中還定義了一個(gè)展示正弦值表的
show_sin_table()函數(shù),但具體代碼暫時(shí)沒有實(shí)現(xiàn),只
用一個(gè)pass語句代替,這正是pass語句發(fā)揮功效的情景。這段代碼也更好地展示了為什么說函數(shù)可
以使代碼模塊化。代碼中的函數(shù)各負(fù)其責(zé),彼此配合,與主程序一起共同完成目標(biāo)。規(guī)劃函數(shù)時(shí)要明確函數(shù)名、參數(shù)與返回值等,另外不要試圖在一個(gè)函數(shù)中做太多的事情,要保證每個(gè)函數(shù)的任務(wù)都不太重。6.3.1函數(shù)的嵌套使用生活中,很多任務(wù)往往都需要一個(gè)團(tuán)隊(duì)來完成,這時(shí)團(tuán)隊(duì)中每個(gè)成員就好比程序中的一個(gè)函數(shù)。
每個(gè)函數(shù)有自己的功能,每個(gè)成員有自己的職責(zé)。一個(gè)函數(shù)可供程序中其他部分的代碼來調(diào)用,一個(gè)成員的職責(zé)也供團(tuán)隊(duì)中其他的成員來調(diào)用。合理地劃分各函數(shù)的功能,恰當(dāng)?shù)卦O(shè)置各函數(shù)的參數(shù)
對(duì)程序是很重要的;同樣的,合理地分割團(tuán)隊(duì)中各成員的職責(zé),恰當(dāng)?shù)卦O(shè)置成員之間溝通的渠道對(duì)于一個(gè)團(tuán)隊(duì)的戰(zhàn)斗力來說也是至關(guān)重要的。甚至即使是一個(gè)人也可以看成一個(gè)團(tuán)隊(duì),因?yàn)橐粋€(gè)人的
人生是由不同的時(shí)空經(jīng)驗(yàn)拼合而成的。人們可以將不同時(shí)空中自己的經(jīng)歷封裝成一個(gè)個(gè)函數(shù),每個(gè)函數(shù)都有特定的功能。當(dāng)你涉獵某個(gè)學(xué)科的知識(shí)、閱讀某本著作、學(xué)習(xí)某項(xiàng)技能、體驗(yàn)了一種特別的經(jīng)歷、在工作中有了特別的感悟時(shí),都可以將學(xué)到的、看到的、體驗(yàn)到的、感悟到的留存下來。這些留存可以是文字、圖片,甚至是視頻,但一定要經(jīng)過規(guī)劃,易于訪問。日后的自己或別人需要了解這個(gè)學(xué)科、這本著作、這項(xiàng)技能時(shí),可以很方便地調(diào)用當(dāng)初你留下的留存痕跡。這樣,無論何
時(shí)、何地,人們都可以把自己人生的各種經(jīng)歷“函數(shù)”化。經(jīng)年累月下來,一個(gè)人可以在生命中累
積眾多的“經(jīng)歷函數(shù)”,它們可以為自己或他人未來的生活帶來便利。6.3.1函數(shù)的嵌套使用。在本章之前的代碼都只有主程序,沒有自定義函數(shù),整個(gè)程序一條線從頭貫到尾。而以代碼
6.17
為代表的代碼除主程序外,還有多個(gè)自定義函數(shù),程序的執(zhí)行會(huì)在主程序與自定義函數(shù)之間跳躍,
不再是簡單的從頭貫到尾的一條線。程序的“領(lǐng)地”被分成了多個(gè)部分,主程序是一部分,各自定
義函數(shù)各為一部分。在程序不同部分出現(xiàn)的變量“影響力”的大小也可能不一樣,這就是變量的作
用域問題。變量的作用域是指變量的有效范圍,類似于生活中人的知名度、影響力。定義在主程序中的
變量為全局變量,其有效范圍原則上為整個(gè)程序,好比公眾人物,全社會(huì)都知曉。而在某個(gè)函數(shù)
內(nèi)部定義的變量其作用域只限于該函數(shù)內(nèi)部,只能在該函數(shù)內(nèi)部使用,這種變量被稱為局部變量,
相當(dāng)于普通素人,只在自己的小圈子里被周圍人知曉。不同的自定義函數(shù)之間無法感知對(duì)方的局
部變量,但各自定義函數(shù)都能感知到主程序中定義的變量,這正體現(xiàn)了主程序中定義的變量的全
局性。例如,前面構(gòu)造正弦數(shù)值表的代碼6.17,仔細(xì)觀察會(huì)發(fā)現(xiàn)在主程序中定義的
SIN_1與CONS_1
變量因?yàn)槭侨肿兞?,所以在多個(gè)自定義函數(shù)中均可直接使用,如函數(shù)build_sin_table()及
sin_next()。而在
build_sin_table()函數(shù)中定義的變量
sin就是一個(gè)局部變量,在其他函數(shù)中是無法訪
問這個(gè)
sin變量的。6.3.2變量的作用域?yàn)榱烁玫匮菔救肿兞颗c局部變量的不同,下面看一段有關(guān)年齡指數(shù)的程序代碼。人年輕時(shí)精
力旺盛,記憶力好,學(xué)什么知識(shí)都快。隨著年齡的增長,人的機(jī)體逐漸衰老,腦力、體力等都會(huì)下降,學(xué)習(xí)能力也會(huì)相應(yīng)下降。因此古人才會(huì)寫出“黑發(fā)不知勤學(xué)早,白首方悔讀書遲”“少壯不努力,老
大徒傷悲”“青春須早為,豈能長少年?”等詩句。下面的代碼
6.18根據(jù)年齡給出人在學(xué)習(xí)能力上的
表現(xiàn)指數(shù)及老年、中年、青少年的分類,研讀代碼時(shí)要重點(diǎn)關(guān)注其中的變量是全局的還是局部的。6.3.2變量的作用域代碼
6.18
理解全局變量與局部變量6.3.2變量的作用域defage_index():age_ix=
0if
age
>
60:age_ix=
0.3elifage
>
45:age_ix=
0.6elifage
>
18:age_ix=
0.9else:age_ix=
1.0returnage_ixdefage_category():age_c=
""if
age
>
60:age_c=
"老年"elifage
>
45:age_c=
"中年"elifage
>
18:age_c=
"青年"else:age_c=
"未成年"returnage_cdefage_info_mixer():returnage_c+str(age_ix)
#嘗試訪問
age_c,報(bào)錯(cuò)#主程序age
=
23print("年齡指數(shù):",age_index())print("分類年齡:",age_category())print("混合信息:",age_info_mixer())代碼運(yùn)行后,主程序中的前兩個(gè)print()函數(shù)可以正常輸出結(jié)果,但最后一個(gè)輸出混合信息的print()
函數(shù)會(huì)出問題,問題在于其調(diào)用了
age_info_mixer()函數(shù),這個(gè)函數(shù)試圖訪問在
age_category()函數(shù)中
定義的局部變量
age_c。代碼
6.18
的運(yùn)行結(jié)果如下,注意錯(cuò)誤提示信息。年齡指數(shù):0.9分類年齡:
青年Traceback
(mostrecentcall
last):File"D:\代碼示例\代碼
6.18-理解全局變量與局部變量.py",line31,in<module>
print("混合信息:",age_info_mixer())File"D:\代碼示例\代碼
6.18-理解全局變量與局部變量.py",line26,inage_info_mixer
returnage_c+str(age_ix)
#嘗試訪問
age_c,報(bào)錯(cuò)NameError:name
'age_c'isnotdefined知道了程序的運(yùn)行結(jié)果后,再來仔細(xì)分析產(chǎn)生這種結(jié)果的原因。代碼在主程序中定義了一個(gè)變
量
age,初值為
23。而后主程序調(diào)用
age_index()函數(shù)和
age_category()函數(shù),這兩個(gè)函數(shù)可以正常返
回
23
歲對(duì)應(yīng)的年齡指數(shù)和年齡分類。但無論是
age_index()函數(shù)還是
age_category()函數(shù),其內(nèi)部并
沒有定義
age
變量,這兩個(gè)函數(shù)也沒有參數(shù),因此這兩個(gè)函數(shù)內(nèi)部代碼中出現(xiàn)的
age
變量只能是主
程序中定義的初始值為23的全局變量
age。這說明在這兩個(gè)函數(shù)中都能正常地訪問主程序定義的
age
變量,證明了
age
變量的全局性。主程序在調(diào)用第三個(gè)函數(shù)
age_info_mixer()時(shí)會(huì)出問題,因?yàn)檫@個(gè)
函數(shù)內(nèi)部要訪問
age_c變量,但
age_c
變量在
age_info_mixer()函數(shù)內(nèi)部沒有被定義,在主程序中也
沒有被定義,事實(shí)上在age_category()函數(shù)中定義了一個(gè)age_c變量,但它是局部變量,只能在
age_category()函數(shù)內(nèi)部使用,即只有
age_category()函數(shù)內(nèi)部這個(gè)“小圈子”才知道
age_c變量的存
在。因此
age_info_mixer()函數(shù)是無法感知到變量
age_c
的,也就是說函數(shù)甲是無法訪問到在另一個(gè)
函數(shù)乙中定義的局部變量的。6.3.2變量的作用域既然age_info_mixer()函數(shù)無法正常工作,下面就來修改它,讓其可以正常運(yùn)行。假設(shè)
age_info_mixer()函數(shù)想要完成這樣一件工作:首先將全局變量age的值改為65,然后分別調(diào)用
age_index()和
age_category()函數(shù)得到兩個(gè)結(jié)果,最后將這兩個(gè)結(jié)果連接起來輸出。按照上面的描述,
使用
age_info_mixer()函數(shù)的形式如代碼
6.19所示。代碼
6.19局部變量對(duì)全局變量的遮蔽6.3.2變量的作用域defage_index():#代碼未變化,在此省略defage_category():#代碼未變化,在此省略defage_info_mixer():age
=
65age_info=age_category()+"的年齡指數(shù)為"+
str(age_index())print("在mixer
函數(shù)中輸出
age
變量:",age)print("在mixer
函數(shù)中調(diào)用另外兩個(gè)函數(shù)的結(jié)果:",age_info)age=
23
#全局變量age_info_mixer()print("在主程序中輸出
age
變量:",age)這段程序沒有達(dá)到修改全局變量
age為
65
的目標(biāo),它的運(yùn)行結(jié)果如下。在mixer
函數(shù)中輸出
age
變量:65在mixer
函數(shù)中調(diào)用另外兩個(gè)函數(shù)的結(jié)果:
青年的年齡指數(shù)為
0.9在主程序中輸出
age
變量:23可以看出,雖然在
age_info_mixer()函數(shù)中試圖給變量
age賦值為
65,但從另外兩個(gè)函數(shù)得
到的年齡指數(shù)和分類年齡來看,它們?nèi)匀徽J(rèn)為全局變量
age
的值是
23,因此才會(huì)有“青年的年
齡指數(shù)為0.9”的輸出結(jié)果。代碼最后,主程序中的print()函數(shù)輸出的age
值也是23,只有在age_info_mixer()函數(shù)內(nèi)部輸出age
時(shí)顯示的才是65。這個(gè)現(xiàn)象只能用一個(gè)原因來解釋,age_info_mixer()函數(shù)中的
age變量根本不是全局的
age變量。也就是說,age_info_mixer()函數(shù)內(nèi)的代碼“age=65”其實(shí)是在自己的局部范圍定義了一個(gè)全新的變量,只不過恰巧這個(gè)變量也叫
age,
除重名外它和取值為
23
的全局變量
age
毫無關(guān)系。這個(gè)取值為
65
的
age是一個(gè)局部變量,只在age_info_mixer()函數(shù)這個(gè)小范圍內(nèi)有效,其他函數(shù)及主程序根本不知道這個(gè)局部age
變量,它們只知曉全局的值為
23
的
age變量。這種現(xiàn)象是一種遮蔽效應(yīng),也就是全局變量
age在
age_info_mixer()函數(shù)這個(gè)局部范圍被一個(gè)同名的局部變量給遮蔽了,導(dǎo)致在age_info_mixer()函數(shù)內(nèi)部訪問age變量其實(shí)都是訪問的局部變量
age。這就像班里有一個(gè)學(xué)生叫李白,在這個(gè)小圈子里日常的點(diǎn)名、同學(xué)之間打招呼時(shí),大家喊“李白”其實(shí)說的是身邊的這個(gè)同學(xué),而不是唐朝的舉世皆知的大詩人。全局的“李白”在班級(jí)這個(gè)局部范圍被一個(gè)普通人李白遮蔽了。6.3.2變量的作用域難道在這個(gè)班里就無法談?wù)撛娙死畎琢藛???dāng)然不是。如果要談?wù)撛娙死畎?,則只需加一個(gè)聲
明即可。例如,班上的老師、同學(xué)可能會(huì)說“大詩人”李白如何、如何?!按笤娙恕边@個(gè)
3個(gè)字就
是一個(gè)聲明記號(hào),有了這個(gè)記號(hào)學(xué)生就知道接下來的“李白”二字是指全局的、世人皆知的李白,
而不是班上這個(gè)局部的李白同學(xué)。同樣的,在
age_info_mixer()函數(shù)中如果想要修改全局的
age變量,
就像在班里要談?wù)撊掷畎滓粯?,加一個(gè)聲明記號(hào)即可,這個(gè)記號(hào)就是
global。下面的代碼
6.20
只
是多了一行
globalage,結(jié)果就變了。代碼
6.20在自定義函數(shù)中聲明全局變量6.3.2變量的作用域代碼
6.20
的運(yùn)行結(jié)果如下,主程序中的變量
age真的變?yōu)?/p>
65
了,另外兩個(gè)函數(shù)的結(jié)果也隨之變
為
65歲對(duì)應(yīng)的結(jié)果。#前兩個(gè)函數(shù)沒有變化,在此省略defage_info_mixer():globalage#聲明本函數(shù)內(nèi)部出現(xiàn)的
age
為全局的ageage
=
65age_info=age_category()+"的年齡指數(shù)為"+
str(age_index())print("在mixer
函數(shù)中輸出
age
變量:",age)print("在mixer
函數(shù)中調(diào)用另外兩個(gè)函數(shù)的結(jié)果:",age_info)age=
23
#全局變量age_info_mixer()print("在主程序中輸出
age
變量:",age)在mixer
函數(shù)中輸出
age
變量:65在mixer
函數(shù)中調(diào)用另外兩個(gè)函數(shù)的結(jié)果:老年的年齡指數(shù)為
0.3在主程序中輸出
age
變量:65這段代碼在age_info_mixer()函數(shù)的開頭先做了聲明,于是該函數(shù)內(nèi)的age變量就是全局變量
age,接下來的“age=65”是在修改全局變量
age,而不是定義了一個(gè)新的局部變量。這樣再調(diào)用另
外兩個(gè)函數(shù)時(shí),那兩個(gè)函數(shù)在它們內(nèi)部代碼訪問到的全局
age
的值已經(jīng)變?yōu)?/p>
65,因此輸出結(jié)果應(yīng)該是“老年的年齡指數(shù)為
0.3”。而主程序最后的
print()函數(shù)輸出的
age值再次印證了全局的
age
的值
已經(jīng)變?yōu)?/p>
65
了。PART46.4遞歸第一次聽說函數(shù)的遞歸調(diào)用會(huì)覺得有點(diǎn)奇怪,一個(gè)函數(shù)怎么能自己調(diào)用自己呢?還是通過一個(gè)求階乘的例子來見識(shí)一下遞歸的真容吧。求階乘使用循環(huán)結(jié)構(gòu)也很容易實(shí)現(xiàn),但下面的代碼
6.21沒有使用循環(huán),而是定義了一個(gè)求階乘的函數(shù),關(guān)鍵在于在這個(gè)函數(shù)的定義中再次調(diào)用了自己。代碼
6.21
使用遞歸求解階乘代碼的運(yùn)行結(jié)果與普通循環(huán)思路求解的答案是一樣的,那么
factorial()函數(shù)是如何不用循環(huán)就完成了階乘的求解呢?其實(shí)遞歸的思路還是一種拆分的思想,它將要解決的問題拆分為兩部分,其中一部分特別容易解決,而另外一部分則是規(guī)模變小了的原問題。例如,求n!的問題可以拆分為
n×(n-1)!,其中
n
與另外一個(gè)數(shù)做乘法是特別容易的問題,而(n-1)!則是規(guī)模變小了的原問題,因此可以繼續(xù)進(jìn)行拆分,即(n-1)!=(n-1)×(n-2)!,如此,這個(gè)拆分過程必然會(huì)進(jìn)行到
2!=2×1!,而1!則是規(guī)模小到極點(diǎn)的原問題,以至于可以直接給出答案。這樣之前的拆分過程都有了一個(gè)堅(jiān)實(shí)的基礎(chǔ),由
1!乘以
2可得
2!,由
2!乘以
3可得
3!,直至得到
n!,從而原問題得解。6.4.1函數(shù)的遞歸deffactorial(n):if
n
==
1:return
1else:returnn*
factorial(n-1)#調(diào)用自己#主程序n
=
10print(f"{n}!=
{factorial(n)}")#輸出:10!=3628800上面的分析已經(jīng)剖析出了遞歸思想的兩個(gè)要素:①每次遞歸(函數(shù)自我調(diào)用)一定要將求解問
題的規(guī)模變小;②最終一定會(huì)有一個(gè)通常很小規(guī)模的原問題可以直接給出答案。而
factorial()函數(shù)中
if
語句的兩個(gè)分支恰恰就是分別完成遞歸的兩個(gè)要素。①如果傳入的參數(shù)
n等于
1,則結(jié)果就是
1,這是整個(gè)遞歸過程的底。②如果傳入的參數(shù)
n大于
1,根據(jù)
n!=n×(n-1)!,將問題化解為求
n-1
的階乘。其方法當(dāng)然就是
再次調(diào)用
factorial()函數(shù),只不過傳入的參數(shù)為
n-1。這樣問題就變?yōu)榕c原問題本質(zhì)相同但規(guī)模變小的問題了。上面的分析只能讓讀者理解靜態(tài)的
factorial()函數(shù)代碼,對(duì)于它層層調(diào)用自己的執(zhí)行過程還是沒有一個(gè)感性認(rèn)知。6.4.1函數(shù)的遞歸下面就以求
3
的階乘為例來分析函數(shù)遞歸調(diào)用的動(dòng)態(tài)執(zhí)行過程。執(zhí)行代碼
6.21
時(shí)首先會(huì)遇到圖
6.2
中標(biāo)記為
A
的代碼行,此處會(huì)調(diào)用求階乘函數(shù)
factorial(),并將
3傳遞給該函數(shù)。這樣就來到了圖
6.2
中標(biāo)記為
B
的位置,這是第
1次對(duì)階乘函數(shù)的調(diào)用,參數(shù)
n
的值為
3,如
圖
6.2
中的矩形框所示。因?yàn)?/p>
n為
3,所以
if
語句進(jìn)入
else分支,從圖
6.2
中可見,else分支中
return
語句后的
n
已經(jīng)被替換為
3,同時(shí)
return語句處發(fā)生對(duì)階乘函數(shù)的第
2次調(diào)用,傳入?yún)?shù)值
n-1
即
2。這樣就來到了圖
6.2
中標(biāo)記為
C
的位置,因?yàn)閭魅氲膮?shù)值為
2,所以對(duì)于標(biāo)記
C
的窗體而言
n為
2,因此還是進(jìn)入
if
語句的
else分支。從圖
6.2
中可見,else分支的
return
語句后的
n
已替換
為
2,然后又發(fā)生對(duì)階乘函數(shù)的第
3次調(diào)用,傳入?yún)?shù)值
n-1
即
1,從而來到了圖
6.2
中標(biāo)記為
D
的位置。因?yàn)閭魅雲(yún)?shù)值為
1,所以
if
語句終于進(jìn)入了第一個(gè)分支。6.4.1函數(shù)的遞歸到這里要明確一點(diǎn),標(biāo)記
A、
B、C
的
3次函數(shù)調(diào)用都還沒有返回,處于懸而未決的狀態(tài),它們的工作場景都在計(jì)算機(jī)內(nèi)存中保
持著。直到現(xiàn)在來到了
D處要返回
1,這個(gè)
1會(huì)返回給調(diào)用者,因此
1會(huì)替換
C處的
factorial(1),
從而
C
處的
return
語句可以返回
2×1
即
2
給它的調(diào)用者,也就是
B
處。因此
2
會(huì)替換
B
處的
factorial(2),這樣
B處的
return語句可以返回
3×2
即
6
給它的調(diào)用者,也就是
A處,所以
6
會(huì)替換
A
處的
factorial(n),這樣之前懸而未決的場面隨著一系列的返回操作而收?qǐng)?,最終的結(jié)果就呈現(xiàn)在
A處了。6.4.1函數(shù)的遞歸
圖
6.2
函數(shù)遞歸調(diào)用的過程遞歸思想的特點(diǎn)是不正面、直接、勤懇地解決面對(duì)的問題,而是“想偷點(diǎn)懶”,看看能不能將
問題規(guī)模變小。它的出發(fā)點(diǎn)是如果已知小規(guī)模同質(zhì)問題的答案,就能輕易地告知最終問題的答案。
而要想知道小規(guī)模同質(zhì)問題的答案,需要知道更小規(guī)模同質(zhì)問題的答案。這樣遞歸直到歸結(jié)到一個(gè)規(guī)模非常小、一看便知結(jié)果的同質(zhì)問題,最后整個(gè)問題就都解決了。因此在設(shè)計(jì)遞歸算法時(shí),不要去關(guān)注問題的直接正面求解,那不是遞歸的設(shè)計(jì)思路。遞歸思想只關(guān)注兩點(diǎn):如何將原問題化解為同質(zhì)的小規(guī)模問題,有了這一點(diǎn),遞歸就被啟動(dòng)了。還有一點(diǎn)就是確保一定有一個(gè)淺顯直白的“小規(guī)?!蓖|(zhì)問題,這是遞歸最終的立足點(diǎn)。6.4.2理解遞歸思想第
3
章介紹循環(huán)時(shí)也提到過循環(huán)結(jié)構(gòu)的構(gòu)思過程。遞歸與循環(huán)是兩種非常典型的思維方式。循
環(huán)其實(shí)對(duì)應(yīng)著另一種思維過程——迭代,而遞歸通常以函數(shù)形態(tài)出現(xiàn)。很多問題既可以有迭代解法,
也可以有遞歸解法。但遞歸的計(jì)算量可能會(huì)更大,另外在遞歸觸底之前,所有遞歸調(diào)用都要保留“作
案現(xiàn)場”,一層層“遞”下去。等著遞歸觸底后,再一層層“歸”來收拾之前的“作案現(xiàn)場”。這個(gè)過程需要保存的現(xiàn)場數(shù)據(jù)比較多,比較消耗內(nèi)存,因此在具體程序語言中,遞歸的層數(shù)都會(huì)有限制。讀者可以嘗試使用代碼
6.21求
1000
的階乘,看看會(huì)發(fā)生什么。6.4.2理解遞歸思想遞歸是計(jì)算機(jī)科學(xué)中一種非常有魅力的解決問題的思維方法,有些問題很難找到正面、直接的
解法,如著名的漢諾塔問題,此時(shí)遞歸就很有用了。但說到底,遞歸方法還是計(jì)算思維中將難題進(jìn)行拆解的一種體現(xiàn),不同的是,遞歸將一個(gè)問題拆解為兩類子問題,一類是顯然可解的子問題,另一類是規(guī)??s小的同質(zhì)原問題。這樣原問題的求解就歸結(jié)為顯然可解的部分加上小規(guī)模的同質(zhì)原問題,而小規(guī)模的同質(zhì)原問題又可以進(jìn)行類似的拆解。6.4.2理解遞歸思想將難題進(jìn)行拆解是計(jì)算思維中的典型招數(shù),如果拆成的幾個(gè)小問題還是比較復(fù)雜,那就繼續(xù)拆
解,直到每個(gè)小問題都比較清晰易解。例如,一個(gè)復(fù)雜的計(jì)算機(jī)系統(tǒng),其實(shí)可拆解為軟硬兩部分。
而無論硬件還是軟件部分都還很復(fù)雜,所以繼續(xù)拆解。軟件部分可以拆解為很多層次,從底層的操作系統(tǒng)到面向用戶的應(yīng)用軟件;硬件部分可以拆解為多個(gè)相對(duì)獨(dú)立的部分,從輸入輸出設(shè)備到
CPU。
CPU還是復(fù)雜,因此繼續(xù)拆解為運(yùn)算器和控制器。又如,設(shè)計(jì)計(jì)算機(jī)網(wǎng)絡(luò)的協(xié)議是一件非常困難的
事情,紛繁復(fù)雜的網(wǎng)絡(luò)規(guī)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 汕頭大學(xué)《中學(xué)語文教學(xué)設(shè)計(jì)與技能訓(xùn)練(一)》2023-2024學(xué)年第一學(xué)期期末試卷
- 食堂與學(xué)校合同范例
- 陜西郵電職業(yè)技術(shù)學(xué)院《公共建筑設(shè)計(jì)Ⅳ》2023-2024學(xué)年第一學(xué)期期末試卷
- 2024至2030年芥菜種子項(xiàng)目投資價(jià)值分析報(bào)告
- 工廠車間包合同范例
- 2024至2030年油泵外殼項(xiàng)目投資價(jià)值分析報(bào)告
- 情侶婚紗租賃合同范例
- 2024至2030年實(shí)驗(yàn)室敞口高剪切乳化機(jī)項(xiàng)目投資價(jià)值分析報(bào)告
- 2024至2030年便攜式涂料攪拌器項(xiàng)目投資價(jià)值分析報(bào)告
- 賠付違約金并解除合同范例
- 產(chǎn)品研發(fā)項(xiàng)目立項(xiàng)書模板
- 自然科學(xué)基礎(chǔ)綜合練習(xí)及答案
- 生物藥物分析練習(xí)題考試題及詳細(xì)答案
- (完整版)自由泳教案
- 期浙江省金華市2023-2024學(xué)年十校聯(lián)考最后歷史試題含解析
- 桌面云項(xiàng)目POC測試報(bào)告
- 網(wǎng)頁視覺設(shè)計(jì)智慧樹知到期末考試答案章節(jié)答案2024年湖南應(yīng)用技術(shù)學(xué)院
- 刑事缺席審判制度探討
- 船舶險(xiǎn)課件-PICC-沈于暉課件
- 在線網(wǎng)課知慧《中學(xué)政治教學(xué)論(渭南師范學(xué)院)》單元測試考核答案
- 國開2024年《機(jī)械設(shè)計(jì)基礎(chǔ)》形考任務(wù)1-4答案
評(píng)論
0/150
提交評(píng)論