Python 程式的撰寫風格:. ✶ 函式前後均空2 行. ∗ 函式的參數. ▸ 函式可接受參數,並由呼叫程式來提供,例如:. ✶ 函式定義:. def printSum(x, y): print(f'兩個 ...
序言
Python簡介
執行Python程式
Python的資料
除錯
小烏龜繪圖模組
Python模組
字串
串列與字組
字典
決策結構
迴圈
函式
檔案
例外處理
物件與類別
第12章 函式
(1)函式
∗函式(Function):具有名稱的一群指令
▸函式的目的:
✶提供其他程式多次呼叫(Functioncall),以避免重複撰寫程式碼
→DRY(Don’trepeatyourself)
✶將相關的程式指令組在一起,讓整體程式更為模組化(Modularization),更符合我們了解問題解決的方式
#例如薪資系統功能可分為以下4個函式:
*分成4個小函式,個別的問題較為獨立且單純,更易於解決
▸函式的語法:
def():
✶def:函式定義(define),是複合指令,因此需以冒號結尾,且其本體指令群需縮排一層
✶:函式的名稱(不可使用Python保留字,且需符合Python識別字格式的規定)
✶:函式的參數(0或多個)
✶:函式的本體指令群
▸Python程式的撰寫風格:
✶函式前後均空2行
∗函式的參數
▸函式可接受參數,並由呼叫程式來提供,例如:
✶函式定義:
defprintSum(x,y):
print(f'兩個數值的和是:{x+y}')
#x與y是函式所接收的參數,亦稱為「正式參數」(Formalparameter)
✶呼叫程式:
a=30
b=50
printSum(a,b)
#a與b是呼叫函式時所提供的參數,亦稱為「實際參數」(Actualparameter)
▸呼叫程式可給不同的參數來多次呼叫函式,以產生不同的結果,如此可避免重複撰寫類似的程式,讓程式效能更高,例如:
a=30
b=50
printSum(a+3,b-5)
printSum(10,20)
printSum(a/3,b/5)
兩個數值的和是:78
兩個數值的和是:30
兩個數值的和是:20.0
∗實際參數與正式參數的對應方式
▸位置參數(Positionalparameter):以位置來相互對應,例如:
✶定義:deffun(a,b,c):
✶以位置對應呼叫:fun(1,2,3)
→a=1,b=2,c=3
▸名稱參數(Namedparameter)或鍵值參數(Keywordparameter):以名稱來相互對應,例如:
✶定義:deffun(a,b,c):
✶以名稱對應呼叫:fun(b=3,c=4,a=5)
→a=5,b=3,c=4
▸預設正式參數值,該參數可以在呼叫時省略,例如:
✶定義:deffun(a,b,c=5):
✶以位置對應呼叫,並省略預設參數:fun(2,3)
→a=2,b=3,c=5
✶無預設值之參數必須放在有預設值之參數之前,例如:
deffun(a,b=4,c):
→SyntaxError:non-defaultargumentfollowsdefaultargument
▸練習1
(2)小烏龜的畫正方形函式
▸範例1:傳入顏色及尺寸,函式繪製正方形
✶利用turtle模組來畫正方形時,我們希望寫相同一段程式來畫不同尺寸的正方形,
而不是針對每種尺寸的正方形都要寫一份程式(否則就要寫無限個程式了!)
draw2Squares.py(首先建立python/ch12目錄)
importturtle
defdrawSquare(t,color,size):
t.color(color)#小烏龜顏色
foriinrange(4):
t.forward(size)#方形邊長
t.left(90)#左轉90度
screen=turtle.Screen()
screen.setup(700,500)
screen.bgcolor('lightyellow')
myTurtle=turtle.Turtle()
myTurtle.shape('blank')#小烏龜沒有形狀
drawSquare(myTurtle,'blue',100)#呼叫函式,給一組參數
myTurtle.left(180)
myTurtle.penup()
myTurtle.forward(100)
myTurtle.pendown()
drawSquare(myTurtle,'red',200)#呼叫函式,給另一組參數
screen.exitonclick()
✶drawSquare()函式:
#其本體指令群縮排一層
#正式參數:t,color,step(小烏龜物件、筆色、及步數),由呼叫程式傳入
#t.color(color):設定小烏龜筆色
#foriinrange(4):總共執行4次之迴圈
*其本體指令縮排一層
*t.forward(size):往前走size距離
*t.left(90):左轉90度
✶呼叫程式:
#從screen=turtle.Screen()指令開始均無縮排,因此從這個指令以下都不屬於
drawSquare()函式的本體
#第一次呼叫drawSquare()函式,畫一個藍色、邊長為100的正方形
#小烏龜移到另一個位置
#第二次呼叫drawSquare()函式,畫一個紅色、邊長為200的正方形
▸範例2:以旋轉方式畫20個尺寸漸大的彩色正方形
draw20Squares.py
importturtle
defdrawColorSquare(t,size):
forcolorin['red','blue','orange','green']:
t.color(color)
t.forward(size)
t.left(90)
screen=turtle.Screen()#設定螢幕與特性
screen.setup(800,600)
myTurtle=turtle.Turtle()#產生小烏龜
myTurtle.pensize(2)
myTurtle.speed(10)
size=20#最小正方形的尺寸
foriinrange(20):
drawColorSquare(myTurtle,size)
size+=10#每次增加方形尺寸
myTurtle.forward(10)#往前走10步
myTurtle.right(18)#右轉10度
screen.exitonclick()
✶drawColorSquare()函式:依照尺寸參數繪製彩色方形
✶呼叫程式:
#每次呼叫函式後均加長方形邊長且移動加旋轉
▸練習2
(3)函式的回覆值與執行流程
∗回覆值的函式:函式在執行完畢會回覆值給呼叫程式
▸語法:在函式最後加上return指令,後面接一個或多個值或表示式
def():
...
return
∗函式的執行流程(Executionflow)
▸範例:計算數值的平方
square.py
defsquare(x):
y=x*x
returny
num=5
result=square(num)
print(result)
▸程式的執行:
✶Python首先執行第1行指令:defsquare,Python知道是要定義一個函式,
並且登記函式名稱為square,如果以後遇到square,Python就會認得此名稱
#第2~3行是函式本體指令群,這些指令並不會執行,函式需要透過呼叫才會執行,因此執行流程跳過這些指令
✶接著執行第6行指令:num=5
✶接著執行第7行指令:result=square(num),此指令呼叫函式並將執行流程控制(Flowcontrol)
交給square()函式,從第1行開始執行
✶接著執行第2,3行函式本體指令,return指令不僅回覆一個值,
同時也將流程控制交還給主程式,回到第7行指令將回覆值指派給result變數
✶接著執行第8行指令,最後結束
▸因此,程式執行的流程為行1→6→7→1→2→3→7→8
▸程式定義要在呼叫程式之前,如此Python才會認得,因此以下程式會出現錯誤:
square.py
num=5
result=square(num)
print(result)
defsquare(x):
y=x*x
returny
Traceback(mostrecentcalllast):
File"square.py",line2,in
result=square(num)
NameError:name'square'isnotdefined
→square名稱未定義,Python不認得
▸如果一個函式沒有寫明回覆值,那麼預設回覆值就是None,例如:
square.py
defsquare(x):
y=x*x
#returny
num=5
result=square(num)
print(result)
None
(4)函式的參數與變數
∗函式的參數與函式內的變數都是區域變數
▸在上例的函式中,有參數x及變數y,
這兩者都只能在函式內部使用,在函式外面就無法使用,因此稱為區域變數(Localvariable),例如:
square.py
defsquare(x):
y=x*x
returny
num=5
result=square(num)
print(result)
print(x)
Traceback(mostrecentcalllast):
File"square.py",line9,in
print(y)
NameError:name'x'isnotdefined
→x名稱未定義,Python不認得(若將x改為
y後,同樣錯誤也會發生:Python也不認得)
#註:在函式中,在指派指令左方使用變數名稱,Python就會產生一個區域變數
▸結論:
✶區域變數只能在其所定義的區域中使用,在該區域以外就無法使用
✶區域變數的壽命:函式開始執行時開始生命,函式執行完畢就結束
∗全域變數(Globalvariable)
▸變數在所有地方都可使用
▸例如:
badSquare.py
defsquare(x):
y=x**power
returny
power=2
result=square(10)
print(result)
✶power定義在主程式,屬全域變數,在函式中亦可使用
▸注意:在函式裡使用全域變數是很不好的作法
(例如全域變數名稱修改,函式中的名稱也要修改),應該避免
▸解決方案:在函式中絕對不要使用全域變數,應該以參數方式將值傳給函式,例如:
goodSquare.py
defsquare(x,power):
y=x**power
returny
power=2
result=square(10,power)
print(result)
▸當區域變數名稱與全域變數名稱相同,區域變數會遮蓋(Shadow)全域變數,例如:
shadowSquare.py
defsquare(x):
power=4
y=x**power
returny
power=2
result=square(10)
print(result)
print(power)
10000
2
✶在函式內的指派指令power=4會產生新的區域變數,不會使用全域變數
(全域變數遭到遮蓋),因此有兩個power變數同時存在
✶最後一行指令會印出2,顯示全域變數power的值並未被修改
(5)使用主函式
∗主函式(Mainfunction)
▸至目前為止,我們的程式包含主程式及函式
▸將主程式轉為主函式是個不錯的作法,可以使我們的程式更模組化(Modularization)
▸例如繪製簡單方形的程式:
drawSquare.py
importturtle
defdrawSquare(t,step):
foriinrange(4):
t.forward(step)
t.left(90)
screen=turtle.Screen()
screen.setup(400,300)
screen.bgcolor('lightyellow')
myTurtle=turtle.Turtle()
drawSquare(myTurtle,100)
screen.exitonclick()
✶可以將主程式改寫為main()函式,並在最後一行呼叫main():
drawSquareMain.py
importturtle
defmain():
screen=turtle.Screen()
screen.setup(400,300)
screen.bgcolor('lightyellow')
myTurtle=turtle.Turtle()
drawSquare(myTurtle,100)
screen.exitonclick()
defdrawSquare(t,step):
foriinrange(4):
t.forward(step)
t.left(90)
main()
✶現在程式結構如下:
1.匯入turtle模組
2.定義主函式main()
3.定義drawSquare()函式
4.呼叫主函式
✶最後才呼叫main()的效果:Python已讀取所有函式定義,因此
drawSquare()函式可以放在main()之後,Python會認得
∗進一步模組化:漂亮的程式架構!
▸將程式分成模組,每個模組有其功能,例如以上程式可以規劃成五個模組:main(),
createScreen(),createTurtle(),drawSquare(),exitScreen()
drawSquareModule.py
importturtle
defmain():
screen=createScreen(400,300,'lightyellow')
myTurtle=createTurtle()
drawSquare(myTurtle,100)
exitOnClick(screen)
defcreateScreen(width,height,bgcolor):
screen=turtle.Screen()
screen.setup(width,height)
screen.bgcolor(bgcolor)
returnscreen
defcreateTurtle():
returnturtle.Turtle()
defdrawSquare(t,step):
foriinrange(4):
t.forward(step)
t.left(90)
defexitOnClick(screen):
screen.exitonclick()
main()
✶如此,主函式的內容變得非常清楚,就是4項工作:產生螢幕、產生小烏龜、畫方形、及結束
✶程式的可讀性更高了,如果不在乎個別函式的內容,甚至可以不需要了解(有可能是別的工程師負責撰寫,有可能在別的檔案),
我們只要看主函式就知道這份程式的功能了
∗支援程式匯入而不立即執行
▸我們所寫的程式也可以提供他人匯入使用,別人使用我們的程式的方式是先匯入,也就是不立即執行,
然後在需要的時候再呼叫我們所寫的函式,就如同我們匯入其他模組一樣:先匯入,需要時再執行
▸但drawSquareModule.py程式最後一行是執行主函式,因此別人匯入時就會立即執行,
不符合匯入需求
▸解決方案:以條件判斷是否要執行main()函式
✶Python提供一個名為__name__的內建變數,如果一個程式檔案被執行了,
__name__的值就會被設定為'__main__'字串
✶因此,我們可以判斷__name__的值,確定程式是被匯入還是被執行,
如果是被執行就呼叫main()函式,否則就不呼叫
✶修改程式:
drawSquareModule.py
...
defexitOnClick(screen):
screen.exitonclick()
main()
if__name__=='__main__':
main()
#最後兩行:判斷__name__是否等於__main__,
如果是,就呼叫main()函式
✶假設有另一個程式需要使用某個模組裡的某個函式或變數,
在該程式需要先匯入模組,然後再呼叫函式,呼叫語法為.()或
.,例如:
callDrawSquareModule.py
importturtle
importdrawSquareModule
defmain():
screen=turtle.Screen()
screen.setup(400,300)
screen.bgcolor('lightgray')
myTurtle=turtle.Turtle()
myTurtle.color('red')
drawSquareModule.drawSquare(myTurtle,100)
screen.exitonclick()
if__name__=='__main__':
main()
#如此程式就更容易分享了
✶註:匯入一個模組只需要寫主檔名,不需加上副檔名.py
(6)匯入在其他套件裡的模組
▸至目前為止,匯入與被匯入的模組都在同一目錄下,但專案常常會依不同功能建立不同的套件目錄,
例如以下專案結構:
project/
packageA/
__init__.py
modA.py
...
packageB/
__init__.py
modB.py
...
...
✶專案(Project)根目錄:project/
✶套件(Package)目錄:packageA/,packageB/,...,因為這些目錄裡都有
__init__.py檔案(內容可以是空的),因此Python認定為套件,允許匯入模組
✶模組(Module):__init__.py,modA.py,modB.py,...
▸假設在modB.py模組中匯入packageA
套件裡的modA模組:
frompackageAimportmodA
modA.drawSquare(...)
...
▸這樣的模組匯入需設定Python路徑,Python才找得到專案的目錄:
✶在Pyzo中設定Python路徑:Shell→Editshellconfiguration(編輯shell組態)→
pythonPath:(專案根目錄路徑)→Done
▸嘗試在if__name__=='__main__':指令前加上一行
print(__name__),分別觀察執行或匯入模組的__name__值
▸練習3