Python 基礎:自訂函數的基礎介紹
Python 基礎:自訂函數的基礎介紹
在 Python 程式設計中,函數(Function)是一個非常重要的概念。無論你是初學者還是有一點基礎的開發者,掌握函數的運用都能讓你的程式碼更加簡潔、有效率且容易維護。本篇文章將帶你深入了解 Python 自訂函數的基礎,並且特別說明全域變數與區域變數的差異,以及常見的錯誤與重點整理,幫助你建立扎實的函數概念。
1. 函數的基本概念
1.1 什麼是函數?
函數(Function)是一段可以重複使用的程式碼區塊,它接受輸入(參數),經過處理後產生輸出(回傳值)。你可以把函數想像是工廠裡的加工機器:把原材料放進去,經過特定處理後,得到我們想要的產品。
在 Python 中,函數主要分為兩大類:
- 內建函數(Built-in Functions):Python 已經幫你寫好的函數,例如
len()、max()、min()、print()等。這些函數可以直接使用,無需額外定義。 - 自訂函數(User-defined Functions):由程式設計師根據需求自行設計的函數。你可以根據要解決的問題,定義自己的函數來完成特定任務。
1.2 為什麼要使用函數?
使用函數的好處有很多:
- 程式碼重用:相同的邏輯只需要寫一次,之後可以無限次呼叫使用。
- 提高可讀性:將複雜的邏輯包裝成具有意義名稱的函數,讓程式更容易理解。
- 方便維護:當需要修改功能時,只需要修改函數內部的程式碼,不用到處尋找並修改。
- 模組化設計:將大型程式拆分成多個小模組,更容易開發與測試。
1.3 生活中的函數比喻
讓我們用生活中的例子來理解函數的概念:
把函數想像是「烤箱」:輸入食材(參數),經過烤箱的處理(函數內部的程式碼),最後得到美味的料理(回傳值)。
或者:
把函數想像是「自動販賣機」:投入硬幣(參數),選擇飲料(函數邏輯),最後掉出飲料(回傳結果)。
2. 自訂函數如何運作
2.1 函數的定義語法
在 Python 中,我們使用 def 關鍵字來定義一個函數。基本語法如下:
1
2
3
def 函數名稱(參數1, 參數2, ...):
# 函數內部的程式碼
return 回傳值
讓我們來看一個簡單的範例:
程式碼範例:攝氏轉華氏
1
2
3
def 攝氏轉華氏(C):
F = 1.8 * C + 32
return F
執行結果
- 如果我們呼叫
攝氏轉華氏(50),會回傳122(華氏溫度)。 - 也就是說,當輸入 C=50 時,經過函數計算,輸出結果為 F=122。
2.2 函數的呼叫方式
要使用一個已經定義好的函數,稱為「呼叫函數」。呼叫語法如下:
1
結果 = 函數名稱(參數值)
例如:
1
2
3
4
5
6
7
def 攝氏轉華氏(C):
F = 1.8 * C + 32
return F
# 呼叫函數
結果 = 攝氏轉華氏(50)
print(結果) # 輸出:122
2.3 參數的傳遞方式
Python 中的參數傳遞是採用「物件參考傳遞」的方式。簡單來說:
- 當你傳遞不可變物件(如數字、字串、元組)時,相當於「傳值」。
- 當你傳遞可變物件(如列表、字典)時,相當於「傳參考」。
這點在設計函數時需要特別注意,否則可能會意外修改到原始資料。
3. 有回傳和不回傳的函數
3.1 有回傳值的函數
使用 return 關鍵字可以將處理結果回傳給呼叫者。函數一旦執行到 return,就會立即結束,並回傳指定的值。
1
2
3
4
5
6
def 加法(a, b):
結果 = a + b
return 結果
總和 = 加法(10, 20)
print(總和) # 輸出:30
3.2 不回傳結果的函數
有些函數只負責執行某些操作,但不需要回傳結果。這類函數通常用於顯示資訊、修改資料結構或產生副作用。
範例:不回傳結果的函數
1
2
3
4
5
def 打招呼(name):
print("哈囉," + name)
# 呼叫函數
打招呼("宅爸") # 輸出:哈囉,宅爸
注意:雖然這個函數沒有使用
return,但它仍然完成了「打招呼」這個任務。在 Python 中,沒有回傳值的函數預設會回傳None。
3.3 return 與 print 的區別
很多初學者會混淆 return 和 print:
print():將結果「顯示」在螢幕上,但不會將結果傳遞給程式其他地方使用。return:將結果「回傳」給呼叫者,可以進一步處理或儲存。
1
2
3
4
5
6
7
8
def 範例1():
print("Hello") # 只會顯示,不會回傳
def 範例2():
return "Hello" # 回傳字串,可以存到變數
結果 = 範例2()
print(結果) # 可以使用回傳的值
4. 全域變數與區域變數
這是 Python 函數中非常重要但常被忽略的概念。正確理解這兩種變數的差異,可以避免很多程式碼的錯誤。
4.1 什麼是區域變數(Local Variable)?
區域變數是指在函數內部定義的變數,只能在該函數內部存取。當函數執行完畢後,這些變數就會被銷毀,無法從函數外部存取。
1
2
3
4
5
6
7
8
9
def 測試區域變數():
x = 10 # 這是區域變數
print("函數內部:", x)
# 呼叫函數
測試區域變數() # 輸出:函數內部:10
# 嘗試在函數外部存取 x
print(x) # 會發生 NameError: name 'x' is not defined
4.2 什麼是全域變數(Global Variable)?
全域變數是指在函數外部定義的變數,可以在程式的任何位置存取(無論是在函數內部還是外部)。
1
2
3
4
5
6
7
8
9
10
y = 20 # 這是全域變數
def 測試全域變數():
print("函數內部:", y) # 可以存取全域變數
# 呼叫函數
測試全域變數() # 輸出:函數內部:20
# 在函數外部也可以存取
print("函數外部:", y) # 輸出:函數外部:20
4.3 在函數內部修改全域變數
如果你想在函數內部修改全域變數,需要使用 global 關鍵字宣告。
1
2
3
4
5
6
7
8
9
10
11
12
count = 0 # 全域變數
def 增加計數():
global count # 宣告我要使用全域變數 count
count = count + 1
print("函數內部:", count)
# 呼叫函數
增加計數() # 輸出:函數內部:1
增加計數() # 輸出:函數內部:2
print("函數外部:", count) # 輸出:函數外部:2
如果沒有使用 global,Python 會把 count = count + 1 視為建立一個新的區域變數,導致錯誤:
1
2
3
4
5
6
count = 0
def 錯誤的範例():
count = count + 1 # 會發生錯誤!
錯誤的範例() # UnboundLocalError: local variable 'count' referenced before assignment
4.4 全域變數與區域變數的比較
| 特性 | 區域變數 | 全域變數 |
|---|---|---|
| 定義位置 | 函數內部 | 函數外部 |
| 存取範圍 | 只能在函數內部 | 整個程式檔案 |
| 生命週期 | 函數執行期間 | 程式執行期間 |
| 記憶體 | 較少(函數結束後釋放) | 較多(直到程式結束) |
| 安全性 | 較高(不會被意外修改) | 較低(可能被多處修改) |
4.5 建議:盡量使用區域變數
一般來說,建議盡量使用區域變數,因為:
- 減少副作用:避免函數意外修改外部狀態,造成難以追蹤的 bug。
- 提高可讀性:函數的輸入輸出更加明確。
- 便於測試:每個函數是獨立的單元,更容易單元測試。
- 執行效率:全域變數的查找比區域變數慢一些。
如果確實需要跨函數共享資料,可以使用參數傳遞或回傳值的方式,這是更好的設計模式。
5. 常見錯誤與解決方案
5.1 忘記 return 或 return 位置錯誤
錯誤範例:
1
2
3
4
5
6
def 求平均值(a, b):
平均 = (a + b) / 2
# 忘記寫 return
結果 = 求平均值(10, 20)
print(結果) # 輸出:None
解決方案:確認函數需要回傳值,並正確使用 return。
5.2 在函數內部修改全域變數(未使用 global)
錯誤範例:
1
2
3
4
5
6
total = 100
def 購物():
total = total - 50 # 沒有宣告 global
購物() # UnboundLocalError
解決方案:如果要修改全域變數,記得使用 global 關鍵字;或者改用參數傳遞和回傳值。
5.3 參數名稱與全域變數名稱衝突
錯誤範例:
1
2
3
4
5
6
7
8
name = "小明"
def 打招呼(name):
print("你好," + name)
打招呼("小華") # 輸出:你好,小華
# 這裡其實是正常的,因為參數會覆蓋區域範圍內的 name
# 但如果沒有傳遞參數,就會出問題
解決方案:注意參數命名,避免與全域變數混淆。如果需要使用全域變數,明確用 global 宣告。
5.4 函數名稱重複定義
錯誤範例:
1
2
3
4
5
6
7
def 加法(a, b):
return a + b
def 加法(a, b, c): # 與上面同名,會覆蓋前面的定義
return a + b + c
print(加法(1, 2)) # TypeError: 加法() missing 1 required positional argument
解決方案:確保每個函數有獨特的名稱,或使用不同的參數數量(但這不是好的設計)。
5.5 預設參數使用可變物件
錯誤範例:
1
2
3
4
5
6
7
8
def 添加到列表(項目, 列表=[]):
列表.append(項目)
return 列表
結果1 = 添加到列表("蘋果")
結果2 = 添加到列表("香蕉")
print(結果1) # ['蘋果', '香蕉']
print(結果2) # ['蘋果', '香蕉'] # 預期應該只有 '香蕉'
解決方案:不要使用可變物件作為預設參數,改用 None:
1
2
3
4
5
def 添加到列表(項目, 列表=None):
if 列表 is None:
列表 = []
列表.append(項目)
return 列表
6. 練習與應用
6.1 練習:判斷閏年
閏年的判斷規則:
- 西元年份除以 4 餘數為 0,且除以 100 餘數不為 0
- 或者西元年份除以 400 餘數為 0
程式碼範例
1
2
3
4
5
6
7
8
def 判斷閏年(year):
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
# 測試
print(判斷閏年(2020)) # True(2020 是閏年)
print(判斷閏年(2021)) # False(2021 不是閏年)
print(判斷閏年(2000)) # True(2000 是閏年)
print(判斷閏年(1900)) # False(1900 不是閏年)
6.2 練習:判斷質數
質數是指只能被 1 和本身整除的大於 1 的整數。
程式碼範例
1
2
3
4
5
6
7
8
9
10
11
12
def 判斷質數(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
# 測試
print(判斷質數(7)) # True
print(判斷質數(10)) # False
print(判斷質數(17)) # True
6.3 練習:計算階乘
程式碼範例
1
2
3
4
5
6
7
8
9
10
def 計算階乘(n):
if n < 0:
return None # 負數沒有階乘
if n == 0 or n == 1:
return 1
return n * 計算階乘(n - 1)
# 測試
print(計算階乘(5)) # 120
print(計算階乘(0)) # 1
6.4 練習:使用區域變數與全域變數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 全域變數
price = 1000
def 計算含稅價格():
稅率 = 0.05 # 區域變數
稅金 = price * 稅率 # 使用全域變數 price
總價 = price + 稅金
return 總價
def 計算折扣價格(折扣):
折扣率 = 折扣 / 100 # 區域變數
折後價 = price * (1 - 折扣率)
return 折後價
print(計算含稅價格()) # 輸出:1050.0
print(計算折扣價格(20)) # 輸出:800.0
print(稅率) # 會錯誤:NameError
7. 重點整理
7.1 函數的核心概念
- 函數定義:使用
def關鍵字定義函數。 - 參數傳遞:函數可以接受零個或多個參數。
- 回傳值:使用
return關鍵字回傳結果,沒有回傳則預設回傳None。 - 函數呼叫:透過
函數名稱()來執行函數。
7.2 全域變數與區域變數
- 區域變數:在函數內部定義,只能在函數內部使用,函數結束後會被銷毀。
- 全域變數:在函數外部定義,可以在整個程式檔案中使用。
- 修改全域變數:在函數內部使用
global關鍵字來宣告。 - 最佳實踐:盡量使用區域變數,減少使用全域變數。
7.3 常見錯誤提醒
- 忘記使用
return回傳結果 - 在函數內部修改全域變數時未使用
global - 參數名稱與全域變數名稱衝突
- 使用可變物件作為函數的預設參數
7.4 為什麼要使用函數?
- 提高程式碼的重用性
- 增加程式的可讀性
- 方便程式的維護與測試
- 實現模組化的程式設計
7.5 學習建議
- 從簡單開始:先從小的、簡單的函數開始練習。
- 命名有意義:函數名稱應該清楚表達它的功能。
- 保持簡潔:每個函數盡量只做一件事。
- 多多練習:將日常生活中的問題轉換成函數來解決。
- 重構舊程式:嘗試把之前寫的程式碼改寫成函數的形式。
自訂函數是 Python 程式設計中不可或缺的基礎概念。透過函數,我們可以將複雜的問題拆分為多個小問題,讓程式碼更加結構化、易於理解和維護。
8. 換你動手做做看囉
程式設計最重要的是動手實作!快來挑戰看看:
- 步驟 1:連到解題網:http://163.30.43.15/
- 步驟 2:註冊帳號且登入,加入課程。
- 課程代碼:
SzNTKb(若已註冊過就不需再註冊)
- 課程代碼:
- 步驟 3:練習完成以下作業:
- 第 9 個作業:YouTube 線上課程 - 課堂作業 自訂函數
加油!寫程式就是不斷試錯與成長的過程。有任何問題歡迎在下方留言!