Python 函數:模組化程式設計的基石 (第 3 章)

站主自己的課程,請大家支持
揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站? 揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站?
  • Post by
  • Dec 17, 2023
post-thumb

在程式設計中,複製貼上 (Copy-Paste) 是萬惡之源。函數 (Functions) 是實現 DRY (Don’t Repeat Yourself) 原則的第一步。它允許我們將一段邏輯封裝起來,並透過參數化來重複利用。

本章將從函數的解剖學開始,深入探討參數傳遞的藝術、變數作用域 (Scope) 的規則,以及 Python 特有的 Lambda 表達式。

1. 函數解剖學 (The Anatomy of a Function)

一個標準的 Python 函數包含定義 (def)、名稱、參數列表、文件字串 (Docstring) 與回傳值。

def calculate_price(price: float, tax_rate: float = 0.05) -> float:
    """
    計算含稅價格。
    
    Args:
        price (float): 原始價格
        tax_rate (float): 稅率 (預設 5%)
        
    Returns:
        float: 含稅總價
    """
    total = price * (1 + tax_rate)
    return total

為什麼需要 Docstrings?

Python 的 docstring (三引號字串) 不僅是註解,它是函數的一部分。使用 help(calculate_price) 或在 IDE 中懸停時,都會顯示這段文檔。這是專業開發者的標準配備。

2. 參數傳遞的藝術

Python 的參數機制非常靈活,掌握它可以讓 API 設計更優雅。

2.1 位置參數 vs 關鍵字參數

def greet(name, message):
    print(f"{message}, {name}!")

# 位置引數 (Positional Argument) - 順序重要
greet("Alice", "Hello")

# 關鍵字引數 (Keyword Argument) - 順序不重要,可讀性高
greet(message="Hi", name="Bob")

2.2 *args**kwargs: 無限可能的參數

當你不確定函數會接收多少參數時,這兩個魔法符號就派上用場了。

  • *args: 接收任意數量的位置參數,打包成 Tuple。
  • **kwargs: 接收任意數量的關鍵字參數,打包成 Dictionary。
def superhero_profile(name, *powers, **details):
    print(f"英雄: {name}")
    print(f"能力: {powers}")  # Tuple: ('Flight', 'Strength')
    print(f"詳細資料: {details}") # Dict: {'age': 30, 'city': 'NY'}

superhero_profile("Superman", "Flight", "Strength", age=30, city="Metropolis")

2.3 強制關鍵字參數 (Keyword-Only Arguments)

在 Python 3 中,我們可以使用 * 來強制某些參數必須使用關鍵字傳遞,這在設定選項較多的函數中非常有用,能避免參數順序錯誤。

# `notify` 必須用關鍵字傳遞
def create_user(username, *, notify=True, content=""):
    print(f"建立使用者 {username}, 通知: {notify}")

create_user("Alice", notify=False) # 正確
# create_user("Alice", False)      # 錯誤!TypeError

3. 變數作用域 (Scope) 與 LEGB 規則

Python 解析變數名稱時,是依照 LEGB 的順序搜尋:

  1. L (Local): 函數內部的區域變數。
  2. E (Enclosing): 外部巢狀函數的變數 (Closure)。
  3. G (Global): 模組層級的全域變數。
  4. B (Built-in): Python 內建名稱 (如 print, len)。
Yes
No
Yes
No
Yes
No
Yes
No
搜尋變數 x
Local 有嗎?
使用 Local x
Enclosing 有嗎?
使用 Enclosing x
Global 有嗎?
使用 Global x
Built-in 有嗎?
使用 Built-in x
NameError

globalnonlocal 關鍵字

若要在函數內修改外部變數,需明確宣告。

counter = 0

def increment():
    global counter  # 宣告我要修改全域變數
    counter += 1

def outer():
    x = 10
    def inner():
        nonlocal x  # 宣告我要修改 Enclosing 變數
        x = 20
    inner()

4. Lambda 表達式:匿名函數

Lambda 是一種定義單行、小型函數的語法糖,常用於高階函數 (如 map, filter, sorted) 中。

語法: lambda arguments: expression

# 傳統寫法
def square(x):
    return x ** 2

# Lambda 寫法
square_lambda = lambda x: x ** 2

# 實戰應用:依據字典的某個鍵值排序
users = [{'name': 'A', 'age': 30}, {'name': 'B', 'age': 20}]
sorted_users = sorted(users, key=lambda u: u['age'])

5. 一級公民 (First-Class Citizen)

在 Python 中,函數是「一級公民」,這意味著:

  1. 函數可以被指派給變數。
  2. 函數可以作為參數傳遞給其他函數。
  3. 函數可以是另一個函數的回傳值。

這為 裝飾器 (Decorators) 等進階模式奠定了基礎(我們將在進階章節深入探討)。

def apply_operation(func, x, y):
    return func(x, y)

def add(a, b): return a + b
def sub(a, b): return a - b

print(apply_operation(add, 5, 3)) # 傳遞函數作為參數

6. 常見問題 (FAQ)

Q1: 什麼時候該用 Lambda?

A: 當你需要一個簡單的、一次性的函數,且內容只有一行表達式時 (如 sorted 的 key)。如果邏輯複雜或需要重複使用,請使用標準 def 定義,以提升可讀性。

Q2: 預設參數的陷阱是什麼?

A: 永遠不要使用可變物件 (如 list, dict) 作為預設參數! 預設參數只會在函數定義時被建立一次,後續呼叫會共用同一個物件。

# ❌ 危險寫法
def append_item(item, list=[]):
    list.append(item)
    return list

print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] !? 竟然共用了

# ✅ 正確寫法
def append_item(item, list=None):
    if list is None:
        list = []
    list.append(item)
    return list

Q3: returnprint 有何不同?

A: print 只是將結果顯示在螢幕上,對程式邏輯沒有後續影響;return 則是將值回傳給呼叫者,可以將結果存入變數或進行後續運算。函數若沒有寫 return,預設回傳 None

7. 總結

函數是構建大型程式的磚塊。掌握參數的靈活運用與作用域規則,能讓你寫出更健壯、可維護的程式碼。

本章重點回顧:

  • DRY 原則: 封裝重複邏輯。
  • Docstrings: 為函數撰寫文件是好習慣。
  • *args/**kwargs: 處理彈性參數。
  • LEGB: 理解變數搜尋路徑。
  • Lambda: 簡潔的匿名函數。

在下一章中,我們將探討 資料結構 (Data Structures),學習如何使用 List, Dict, Set 來有效地組織與管理數據!


延伸閱讀

LATEST POST
TAG