本文 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("小美"))      # => 哈囉,小美!

重點greetsay_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.CallableProtocol 為函式參數、返回值加上型別。
遺忘返回值 在返回函式的工廠函式中忘記 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 的無限可能!