本文 AI 產出,尚未審核

Python 課程 – 函式(Functions)

主題:函式作為一級公民(First‑Class Function)


簡介

在許多程式語言中,函式(function)不僅僅是執行程式碼的工具,它本身也是一個可以被操作的物件。Python 把函式視為「一級公民」——也就是說,函式可以像變數、數字、字串一樣被 指派、傳遞、儲存、返回,甚至可以動態產生新的函式。

掌握這個概念不只是理論上的好玩,它直接影響到我們寫出 可重用、可組合、可測試 的程式碼。從簡單的回呼(callback)到裝飾器(decorator)或函式式編程(functional programming)風格,所有進階技巧的基礎都建立在「函式是一等公民」之上。

本篇文章會以 淺顯易懂 的方式說明什麼是一級公民函式、它的核心特性、常見的實作模式,以及在真實專案中如何善加利用。


核心概念

1. 函式可以被指派給變數

在 Python 中,函式本身是一個 function 物件。只要把函式名稱(不加括號)寫在等號右側,就會把這個函式物件指派給左側的變數。

def greet(name: str) -> str:
    """傳回問候字串"""
    return f"哈囉,{name}!"

# 把函式指派給另一個變數
say_hello = greet

# 兩個變數實際指向同一個函式物件
print(say_hello("小明"))   # => 哈囉,小明!
print(greet("小美"))      # => 哈囉,小美!

重點:greet 與 say_hello 皆是指向同一個函式物件,呼叫方式完全相同。


2. 函式可以作為參數傳遞(高階函式)

所謂「高階函式」是指 接受函式作為參數 或 返回函式 的函式。最常見的情境是「回呼」──把一段行為交給另一段程式去執行。

from typing import Callable, List

def apply_to_each(items: List[int], func: Callable[[int], int]) -> List[int]:
    """將 func 套用到每個元素,回傳新列表"""
    return [func(item) for item in items]

def square(x: int) -> int:
    return x * x

numbers = [1, 2, 3, 4]
squared = apply_to_each(numbers, square)   # 把 square 函式傳入
print(squared)  # => [1, 4, 9, 16]

技巧:使用 typing.Callable 可以為傳入的函式加上型別提示,提升可讀性與 IDE 的自動補完功能。


3. 函式可以作為返回值(閉包)

函式不僅能被傳入,還能 被其他函式產生並返回。這種模式常用來建立「閉包」──即返回的函式會捕獲外層函式的環境變數。

def make_multiplier(factor: int):
    """產生一個乘以 factor 的函式"""
    def multiplier(x: int) -> int:
        return x * factor          # factor 來自外層作用域
    return multiplier

times_three = make_multiplier(3)   # 產生乘以 3 的函式
times_five  = make_multiplier(5)   # 產生乘以 5 的函式

print(times_three(10))  # => 30
print(times_five(10))   # => 50

說明:multiplier 內部使用了外層的 factor,即使 make_multiplier 已經結束,factor 仍被保留下來,形成閉包。


4. 函式可以儲存在資料結構中

因為函式是物件,我們可以把它放進 列表、字典、集合 等容器,進而實現「策略模式」或「指令模式」。

def add(a: int, b: int) -> int: return a + b
def sub(a: int, b: int) -> int: return a - b
def mul(a: int, b: int) -> int: return a * b

operations = {
    "加法": add,
    "減法": sub,
    "乘法": mul,
}

def calculate(op_name: str, x: int, y: int) -> int:
    """根據 op_name 從字典中取出對應的函式並執行"""
    func = operations.get(op_name)
    if not func:
        raise ValueError(f"不支援的運算:{op_name}")
    return func(x, y)

print(calculate("加法", 7, 5))   # => 12
print(calculate("乘法", 7, 5))   # => 35

5. Lambda(匿名函式)與即時函式

lambda 讓我們 在一行內定義簡短的函式,常與高階函式一起使用。雖然功能上等同於 def,但可讀性與除錯性較差,應適度使用。

# 使用 lambda 計算平方
square = lambda x: x * x
print(square(6))   # => 36

# 在 sorted 中傳入自訂排序鍵
words = ["apple", "banana", "cherry", "date"]
words_sorted = sorted(words, key=lambda w: len(w))
print(words_sorted)   # => ['date', 'apple', 'banana', 'cherry']

常見陷阱與最佳實踐

陷阱 說明 建議的做法
忘記加括號 把函式名稱寫成 func() 會直接執行,失去「傳遞函式」的意圖。 傳遞時僅使用函式名稱(如 apply(func)),不加 ()。
閉包捕獲變數的變化 在迴圈裡建立閉包時,所有閉包會捕獲同一個變數,導致結果相同。 使用 預設參數 或 functools.partial 固定當前值。
過度使用 lambda 內嵌過長的 lambda 會降低可讀性。 當邏輯超過一兩行時,改用 def 定義具名函式。
忽略型別提示 高階函式接受的 Callable 若未標註型別,易產生錯誤。 使用 typing.Callable、Protocol 為函式參數、返回值加上型別。
遺忘返回值 在返回函式的工廠函式中忘記 return,導致返回 None。 檢查每個路徑都有返回值,或使用 mypy 靜態檢查。

最佳實踐

  1. 保持函式純粹(pure):若函式不依賴外部狀態,則更易於作為回呼或傳遞。
  2. 使用 functools.wraps:在寫裝飾器時保留原函式的 __name__、__doc__。
  3. 利用 partial:當需要「預先綁定」部分參數時,functools.partial 是安全又可讀的選擇。
  4. 文件化回呼契約:在函式說明中明確列出回呼函式的參數與回傳型別,避免使用者踩雷。
  5. 測試高階函式:寫單元測試時,使用 假函式(mock) 來驗證回呼是否正確被呼叫。
from functools import partial

def power(base: int, exponent: int) -> int:
    return base ** exponent

# 產生一個「平方」函式
square = partial(power, exponent=2)

print(square(5))   # => 25

實際應用場景

場景 為什麼需要一級公民函式 範例
事件驅動 GUI UI 元件的點擊、變更等需要回呼函式。 Tkinter 的 button.bind("<Button-1>", on_click)
資料處理管線 把資料轉換步驟封裝成函式,串接成 pipeline。 map, filter, reduce 搭配自訂函式
Web 框架的中介層(middleware) 中介層本身是接受 request、返回 response 的函式。 Flask 的 @app.before_request 裝飾器
策略模式 根據不同條件選擇不同演算法,使用字典映射。 前文的 operations 範例
裝飾器(Decorator) 裝飾器本身是返回新函式的高階函式,用於功能擴充。 @login_required、@cache 等

實務貼士:當你在設計 API 或函式庫時,盡量讓使用者能 自行注入回呼,這樣程式的彈性與可測試性都會大幅提升。


總結

  • 函式是一等公民:它可以被指派、傳遞、儲存、返回,甚至在執行時動態產生。
  • 了解 高階函式、閉包、lambda 與 資料結構內的函式,是寫出 可組合、可重用 程式碼的關鍵。
  • 在開發過程中,避免常見陷阱(忘記加括號、閉包捕獲變數等),並遵循 型別提示、文件化契約、純粹函式 等最佳實踐。
  • 真實專案中,從 GUI 事件、資料管線、Web 中介層到策略模式,皆可透過一級公民函式提升程式的彈性與可維護性。

掌握了「函式作為一級公民」的概念,你就能在 Python 的世界裡自由組合功能、打造乾淨的程式結構,為未來學習裝飾器、生成器、協程等更高階主題奠定堅實基礎。祝你寫程式愉快,持續探索 Python 的無限可能!