
Python 入門指南 5.0
單元 10 - 函數

函數 (function) 是一種功能性的程式碼集合,可以將程式 (program) 分割成小部分,藉由呼叫 (call) 函數安排執行順序
pass
定義函數使用關鍵字 (keyword) def ,其後空一格接函數名稱與小括弧,小括弧用來放參數列 (parameter list) ,函數可以有參數 (parameter) 也可以沒有參數,沒有參數的函數的小括弧留空,另外函數可用 return 設定回傳值 (return value) ,沒有回傳值的函數也就不需要 return 。舉一例如下
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 計算參數總合
def my_sum(a, b):
    # 回傳參數相加結果
    return a + b
# 利用整數當參數
print(my_sum(33, 22))
# 利用字串當參數
print(my_sum("Hello", "World"))
# 檔名: function_demo01.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
 | 
my_sum() 函數回傳兩個參數的相加值,參數名稱及數量則是依自己定義放在參數列,這裡的參數用了 a 與 b ,另外此例用了一個 return ,這裡 return 就是函數結束執行,將控制權交還原本呼叫函數的地方
| 2 4 | def my_sum(a, b):
    return a + b
 | 
此例先計算 33 與 22 的合,然後印出計算結果
|  9 | print(my_sum(33, 22))
 | 
接下來則是連接 "Hello" 與 "World" ,加號用在字串 (string) 是把兩個字串連接起來
|  7 | print(print(my_sum("Hello", "World"))
 | 
執行結果如下
| $ python function_demo01.py | 
| 55 | 
| HelloWorld | 
| $ | 
函數參數可以有預設值,參數預設值是用等號在參數列直接指派數值,例如
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 計算參數總合
def my_sum(a = 0, b = 0):
    # 回傳參數相加結果
    return a + b
# 利用關鍵字引數設定參數
print(my_sum(b = 33, a = 22))
# 不提供參數
print(my_sum())
# 檔名: function_demo02.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
 | 
這裡在參數列直接指派數值給參數 a 及 b
|  2 | def my_sum(a = 0, b = 0):
 | 
因此呼叫時可以不提供參數,會以預設值代入
|  9 | print(my_sum())
 | 
執行結果如下
| $ python function_demo02.py | 
| 55 | 
| 0 | 
| $ | 
呼叫函數時可以依參數位置逐一提供參數,或是用關鍵字引數 (keyword argument) 呼叫,在參數列加入斜線 / 或星號 * 等可以限制呼叫方式,或是混用兩種方式,例如
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # 只能用參數位置呼叫
def my_sum1(a, b, /):
    # 回傳參數相加結果
    return a + b
# 只能有關鍵字引數呼叫
def my_sum2(*, a, b):
    # 回傳參數相加結果
    return a + b
# 兩種呼叫方式混合
def my_sum3(a, /, *, b):
    # 回傳參數相加結果
    return a + b
# 印出 11
print(my_sum1(5, 6))
# 印出 22
print(my_sum2(b = 11, a = 11))
# 印出 33
print(my_sum3(11, b = 22))
# 檔名: function_demo03.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
 | 
引數的英文原文為 argument ,參數的英文原文為 parameter ,所謂引數是呼叫函數時提供的實際數值,參數則是定義函數時所用的識別字名稱,兩者其實是同一件事情,之所以會有兩種名稱,這是由於英文的語言習慣是每個地方都要有專門的名詞,在此基於教學立場,除了關鍵字引數為 Python 官方文件的用詞外,其他會統稱為參數,至於引數可能會以參數值統稱。
同一件事情用不同的名詞在英文中屢見不鮮,像是在程式設計領域後面還會碰到 process 一詞, process 通常翻譯成行程,意思是正在被執行的程式,所以是程式原文 program 的進行式狀態名詞,那程式就是寫好還沒有被執行的檔案,然後詳細深入作業系統討論會嚴格把兩者區分,這裡我們在初學一率統稱 program ,也就是程式,因為要用不同詞彙去討論同一件事情的不同面向,這不是初學程式設計馬上能理解的。
斜線 / 必須放在限制位置呼叫參數的最後,例如 my_sum1() 參數列的最後是斜線 / ,表示 a 與 b 都必須以位置呼叫,也就是呼叫時提供的第一個參數為 a ,第二個為 b
| 2 4 | def my_sum1(a, b, /):
    return a + b
 | 
實際呼叫就是預設的方式,但限制只能用這種方式
| 17 | print(my_sum1(5, 6))
 | 
星號 * 必須放在關鍵字引數呼叫參數的開頭,例如 my_sum2() 參數列的開頭是星號 * ,表示 a 與 b 都必須以關鍵字引數呼叫,也就是呼叫時必須以參數名稱設定
| 7 9 | def my_sum1(a, b, /):
    return a + b
 | 
實際呼叫限制使用關鍵字呼叫,呼叫的順序則沒有限制
| 19 | print(my_sum2(b = 11, a = 11))
 | 
如果參數列定義沒有加上斜線 / 或星號 * ,就可以混用位置呼叫或是關鍵字引數呼叫,但是一樣位置呼叫在前,關鍵字引數呼叫在後。
兩者標記也可以混用,斜線 / 之前限制位置呼叫,星號 * 之後為關鍵字引數呼叫
| 12 14 | def my_sum3(a, /, *, b):
    return a + b
 | 
位置呼叫直接提供參數值,關鍵字引數呼叫則要加上參數名稱與等號
| 21 | print(my_sum3(11, b = 22))
 | 
執行結果如下
| $ python function_demo03.py | 
| 11 | 
| 22 | 
| 33 | 
| $ | 
參數數量也可以不受限制,在參數識別字前加上一個星號 * 表示參數為序對 (tuple) ,要以位置參數呼叫,加上兩個星號表示字典 (dictionary) ,要以關鍵字引數呼叫,例如
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # 不限個數參數的序對版本
def my_sum1(*arg):
    # 印出參數型態
    print(type(arg))
    # 暫存回傳結果
    result = 0
    # 依參數計算總合
    for i in arg:
        result += i
    # 回傳參數總合
    return result
# 不限個數參數的字典版本
def my_sum2(**arg):
    # 印出參數型態
    print(type(arg))
    # 暫存回傳結果
    result = 0
    # 依參數計算總合
    for i in arg:
        result += arg[i]
    # 回傳參數總合
    return result
# 利用位置提供參數
print(my_sum1(11, 22, 33))
# 利用關鍵字引數設定參數
print(my_sum2(a = 11, b = 22, c = 33))
# 檔名: function_demo04.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
 | 
my_sum1() 與 my_sum2() 的內容幾乎相同,都是先用內建函數 type() 印出參數的型態 (type) ,然後利用 for 迴圈 (loop) 替參數加總,最後回傳參數的所有相加值,處理上除了 my_sum1() 是序對外, my_sum2() 由於是字典,所以 for 迴圈的 i 是取得 key
| 20 21 |     for i in arg:
        result += arg[i]
 | 
呼叫時關鍵字引數中等號左邊的關鍵字會是 key
| 26 28 | print(my_sum1(11, 22, 33))
print(my_sum2(a = 11, b = 22, c = 33))
 | 
執行結果如下
| $ python function_demo04.py | 
| <class 'tuple'> | 
| 66 | 
| <class 'dict'> | 
| 66 | 
| $ | 
關鍵字 lambda 可以定義簡單的運算式 (expression) ,在函數需要以函數當作參數時,可以替代函數函數當作參數,以下簡單示範使用 lambda
| 1 2 3 4 5 6 7 8 9 10 | # 變數 sum 是 lambda 定義的無名函數
my_sum = lambda x, y: x + y
# 印出 33
print(my_sum(11, 22))
# 檔名: function_demo05.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
 | 
此例 lambda 的定義指派給變數 my_sum , my_sum 就變成需要參數才能使用的函數
| 2 | my_sum = lambda x, y: x + y
 | 
lambda 定義出的運算式也被稱為無名函數 (anonymous function) 。
執行結果如下
| $ python function_demo05.py | 
| 33 | 
| $ | 
關鍵字 yield 用在函數中可以定義產生器 (generator) 函數,例如
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # 定義產生器函數
def number(*arg):
    for i in range(len(arg)):
        # 依次產生引數值
        yield arg[i]
# 印出產生器函數的計算結果
print(sum(number(11, 22, 33)))
# 印出產生器運算式的計算結果
print(sum(i for i in [11, 22, 33]))
# 檔名: function_demo06.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
 | 
所謂產生器就是依序產生數值的意思,至於產生數值的規則可以自訂,此例是依序讓 number() 產生序對中的參數值
| 2 3 5 | def number(*arg):
    for i in range(len(arg)):
        yield arg[i]
 | 
底下用內建函數 sum() 對參數進行加總,注意第 8 行是由產生器函數產生的數值
|  8 | print(sum(number(11, 22, 33)))
 | 
第 10 行則是產生器運算式 (generator expression) 產生的數值
| 10 | print(sum(i for i in [11, 22, 33]))
 | 
關鍵字 for 之前的變數 i 就會產生的數值,此變數 i 就是由 for 在關鍵字 in 後面取得的串列元素,這裡是整數,因此會依次產生 11 、 22 、 33 供 sum() 計算。
產生器的目的是產生數列中的數值,產生器運算式是簡單的產生器寫法,用 for 迴圈直接在複合資料型態 (compound data type) 中取得數值,就是相對數值已經建立好,或是還需額外放在函數中計算,總之是產生器函數的語法糖,而產生器其實也是迭代器 (iterator) 的語法糖,我們會在單元 14 介紹迭代器。
上例執行結果如下
| $ python function_demo06.py | 
| 66 | 
| 66 | 
| $ | 
函數是模組化 (modular design) 程式設計的第一步,繼續模組化程式設計就是要來介紹類別 (class) ,不過在進入類別之前,我們先來進一步討論一下指派運算 (assignment expression) 。
| 中英文術語對照 | |
|---|---|
| 無名函數 | anonymous function | 
| 指派運算 | assignment expression | 
| 呼叫 | call | 
| 複合資料型態 | compound data type | 
| 類別 | class | 
| 字典 | dictionary | 
| 運算式 | expression | 
| 函數 | function | 
| 產生器 | generator | 
| 產生器運算式 | generator expression | 
| 迭代器 | iterator | 
| 關鍵字 | keyword | 
| 關鍵字引數 | keyword argument | 
| 迴圈 | loop | 
| 模組化 | modular design | 
| 參數列 | parameter list | 
| 參數 | parameter | 
| 程式 | program | 
| 回傳值 | return value | 
| 序對 | tuple | 
| 型態 | type | 
| 重點整理 | 
|---|
| 1. 函數是一種功能性的程式碼集合,可以有參數及回傳值,參數不限個數,回傳值最多只能有一個。 | 
| 2. return 陳述將執行控制權還給呼叫函數的地方。 | 
| 3. 如果要給函數的參數設定預設值,就直接在參數列用等號指派數值,呼叫時就可以不提供參數。 | 
| 4. 呼叫函數提供參數時,可以依照參數列順序或是利用關鍵字引數,如果要限制呼叫方式會混用兩者,需要借助 / 或 * 。 | 
| 5. 參數可以不限制數量,在參數前加上星號會取得序對,並限制依順序呼叫,若是加上兩個星號表示字典,限制用關鍵字引數呼叫。 | 
| 6. lambda 運算式可以定義簡單的無名函數。 | 
| 7. 函數中用 yield 而非 return 就是產生器函數。 | 
| 8. 產生器運算式是產生器函數的簡化寫法。 | 
| 問題與討論 | 
|---|
| 1. 為什麼函數要有參數?若在函數中改變參數的值,原本的參數會被改變嗎? | 
| 2. 為什麼函數只能有一個回傳值?如果有需要回傳多個數值該如何處理? | 
| 3. 要怎麼替函數的參數設定預設值? | 
| 4. 要怎麼限制函數的參數要按照順序提供?或是限制使用關鍵字引數? | 
| 5. 什麼是無名函數?為什麼要使用無名函數? | 
| 5. 什麼是產生器函數? | 
| 練習 | 
|---|
| 1. 寫一個程式 exercise1001.py ,裡頭設計一個函數 my_sum() ,用以計算兩個整數的和。 參考程式碼 | 
| 2. 承上題,另寫一個程式 exercise1002.py ,改成接受使用者輸入的版本。 參考程式碼 | 
| 3. 寫一個程式 exercise1003.py ,裡頭設計一個函數 my_sum() ,只用一個整數參數 p ,結果回傳 1 到 p 之間所有正整數的和。 參考程式碼 | 
| 4. 承上題,另寫一個程式 exercise1004.py ,改成接受使用者輸入的版本。 參考程式碼 | 
| 5. 寫一個程式 exercise1005.py ,裡頭設計一個函數 factorial() ,用以計算階乘值。 參考程式碼 | 
| 6. 承上題,另寫一個程式 exercise1006.py ,改成接受使用者輸入的版本。 參考程式碼 | 
| 7. 寫一個程式 exercise1007.py ,裡頭設計一個函數 fibonacci() ,用以計算費氏數列。 參考程式碼 | 
| 8. 承上題,另寫一個程式 exercise1008.py ,改成接受使用者輸入的版本。 參考程式碼 | 
| 9. 寫一個程式 exercise1009.py ,設計印出時間的 print_time() 函數,參數 now 的值預設為 None ,並在函數中確認 None 有值,如果沒有值就指派為現在時間。 參考程式碼 | 
| 10. 寫一個程式 exercise1010.py ,設計一個至少需要三個參數的函數,例如 do_something() ,然後在函數內依據函數的值回傳計算結果,計算方式請自行定義,最後利用關鍵字引數呼叫並印出回傳值。 參考程式碼 | 
| 11. 承上題,將程式寫在 exercise1011.py 中,將參數改為限制位置呼叫。 參考程式碼 | 
| 12. 承上題,將程式寫在 exercise1012.py 中,將參數改為限制用關鍵數引數呼叫。 參考程式碼 | 
| 13. 承上題,將程式寫在 exercise1013.py 中,將參數改為混用位置與關鍵數引數呼叫。 參考程式碼 | 
| 14. 寫一個程式 exercise1014.py ,設計函數採用序對的不限個數參數,然後在函數中印出每個參數。 參考程式碼 | 
| 15. 承上題,將程式寫在 exercise1015.py 中,改用字典的不限個數參數,然後在函數中增加新的 key:value ,例如 a["z"] = "end" ,最後回傳參數 a ,然後在底下執行部分印出每個 key 的 value 。 參考程式碼 | 
| 16. 關鍵字 lambda 通常會用在需要函數當作參數的場合,將程式寫在 exercise1016.py 中,,設計一個具有兩個序對組合的串列,例如 [(12, "A01"), (9, "B33"), (2, "C02")] ,然後利用內建函數 sorted() 進行排序,用 lambda 設定關鍵字引數 key ,使其用序對中的第二個元素進行排序。 參考程式碼 | 
| 17. 承練習 5 ,將新程式寫在 exercise1017.py 中,把計算階乘的函數改為產生器函數。 參考程式碼 | 
| 18. 承練習 7 ,將新程式寫在 exercise1018.py 中,把計算費氏數列的函數改為產生器函數。 參考程式碼 | 
