Python程序設(shè)計(jì)基礎(chǔ)第六章 函數(shù)_第1頁
Python程序設(shè)計(jì)基礎(chǔ)第六章 函數(shù)_第2頁
Python程序設(shè)計(jì)基礎(chǔ)第六章 函數(shù)_第3頁
Python程序設(shè)計(jì)基礎(chǔ)第六章 函數(shù)_第4頁
Python程序設(shè)計(jì)基礎(chǔ)第六章 函數(shù)_第5頁
已閱讀5頁,還剩66頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(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ì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論