Learning Objectives
- 函數(function)的定義方式:def、參數、docstring
- 如何呼叫函數,以及 return 回傳值(含沒有 return 時的 None)
- 用呼叫堆疊看懂函數呼叫函數的執行順序
- 函數傳引數的方式:pass by assignment
為什麼需要函數
寫程式時常常有一段功能要在很多地方用到:計算平均、檢查輸入、印出報表。 如果每次都複製貼上,改一個地方就要改很多份,也很容易漏改。
函數(function) 把一段程式碼包起來、取一個名字,需要時「呼叫」它就好:
- 可重複使用(reusable):寫一次,到處用。
- 模組化:把大問題拆成幾個小功能,每個函數只做一件事,程式更好讀、好測試。
你已經用過很多 Python 內建的函數,例如 print()、len()、max()。這一課要學的是自己寫的
使用者自訂函數(user-defined functions)。
def sum_nums(n1, n2): # def 函數名稱(參數):
"""回傳兩個數字的和""" # docstring:給使用者看的說明
return n1 + n2 # return 結束函數並傳回結果
函數名稱依照 PEP 8 慣例使用英文小寫,多個單字用底線 _ 連接,例如 make_a_sound。
定義與呼叫函數
先注意第一步:執行 def 那一行時,函數內容並不會執行,只是建立一個函數物件,讓名稱 make_a_sound 指向它。之後每次呼叫,右邊會多出一個 make_a_sound 的框架(frame),執行完就消失。
註解與 docstring
函數下面第一行用三個雙引號包起來的字串,稱為 docstring(document string)。
# 註解 |
docstring """...""" |
|
|---|---|---|
| 給誰看 | 寫程式的人自己 | 使用這個函數的人 |
| 能不能在程式裡取得 | 不能 | 可以:函數.__doc__、help(函數) |
| 用在哪裡 | 任何一行 | 函數、模組、類別的開頭 |
參數與回傳值
呼叫時傳進去的值(引數,argument)會指派給函數定義的名稱(參數,parameter)。return 把結果交回呼叫的地方。看最後兩行:printme 沒有寫 return,所以呼叫結果是 None。
呼叫堆疊:函數呼叫函數
函數裡面可以再呼叫別的函數。Python 用呼叫堆疊(call stack)記住「現在在哪個函數裡、做完要回到哪裡」:
- 每呼叫一次函數,堆疊頂端就放上一個新框架(frame),裡面是這次呼叫自己的參數與區域變數。
- 函數
return時,它的框架就被拿掉,執行回到下面那一層、剛才呼叫它的那一行。 - 同一個函數被呼叫兩次,就會有兩個(先後出現的)框架,彼此的變數互不干擾。
看框架出現與消失
這是本課最重要的動畫。一步一步按,眼睛盯著右邊的框架區:呼叫 sum_of_squares 時多出一層、它再呼叫 square 時變成三層;square 回傳後最上層消失,執行回到 sum_of_squares 的下一行。數數看 square 的框架一共出現了幾次。
用元組傳回多個值
return min(nums), max(nums) 其實是把兩個值打包成一個元組傳回;呼叫端可以用上一課的解包一次接住。
想一想:定義、呼叫與回傳
下面的程式會印出什麼?
def add(a, b):
print(a + b, end=" ")
r = add(2, 3)
print(r)
下面的程式會印出什麼?
def f(x):
return x + 1
print("done")
print(f(f(1)))
程式執行到 def square(x): 這一行時,會發生什麼事?
傳進函數的是什麼?Pass by assignment
Python 傳引數的方式稱為 pass by assignment:呼叫 changeme(myvar) 時,就像在函數裡執行了一次
參數 = 引數 的指派。參數和外面的變數指向同一個物件,但它們是兩個不同的名稱。
接下來兩個範例幾乎一模一樣,結果卻不同。關鍵在函數裡做的是哪一種事:
| 函數裡做的事 | 例子 | 外面看得到變化嗎 |
|---|---|---|
| 重新指派參數(讓名稱改指向別的物件) | myvar = 50 |
看不到 |
| 修改參數指向的可變物件 | mylist[2] = 50 |
看得到 |
重新指派整數參數
函數裡和外面都有一個叫 myvar 的名稱,但它們在不同的框架裡。第 4 行只讓函數框架裡的 myvar 改指向 50,模組框架裡的 myvar 仍然是 20。
修改串列參數
這次參數指向一個串列。看右邊的箭頭:函數框架的 mylist 和模組框架的 mylist 指向同一個串列物件。第 4 行修改的是那個物件本身,所以函數結束後,外面也看得到。
追蹤參數傳遞
下面的程式會印出什麼?
def reset(nums):
nums = [0, 0]
data = [5, 6]
reset(data)
print(data)
下面的程式會印出什麼?
def grow(nums, n):
nums.append(n)
n = n * 10
data = [1]
k = 2
grow(data, k)
print(data, k)
實作:計算及格人數
完成函數 count_pass(scores):傳回串列 scores 中大於或等於 60 分的人數。記得用 return 傳回結果,而不是在函數裡 print。
重點精華回顧
- 函數是一段可重複使用的程式碼;
def 名稱(參數):定義,名稱(引數)呼叫。 - 執行
def只建立函數物件,不執行內容;呼叫時才執行。 - docstring 寫在函數第一行,給使用者看,可用
__doc__或help()取得;#註解是給自己看的。 return結束函數並傳回值;沒有return(或只寫return)時傳回None;用元組可以一次傳回多個值。- 每次呼叫都會在呼叫堆疊放上一個新框架,回傳後框架消失,執行回到呼叫的那一行。
- Pass by assignment:參數和引數指向同一個物件。重新指派參數不影響外面;修改可變物件(串列)外面看得到。