跳到主要內容

Day 8 函式進階

Day 8 函式進階

引言

昨天我們認識了 Python 函式的基本功:定義函式、傳遞參數、回傳結果、加上說明字串。大部分日常的程式需求,光靠這些基礎就能應付。但當程式規模逐漸變大,你會遇到一些更靈活的需求——例如函式需要接收不確定數量的引數、想把小型邏輯直接寫成匿名函式、或者需要理解變數在不同範圍的可見性。

這篇文章會帶你認識函式的進階主題:*args 與 **kwargs 用來接收任意數量的引數、LEGB 作用域規則幫助理解變數的可見性、lambda 匿名函式適合用在需要小函式的場合,以及遞迴如何用「自己呼叫自己」的方式拆解問題。這些技巧可以讓你寫出更具彈性的函式,也為日後學習函式型程式設計與深度學習鋪路。

*args:接收任意數量的位置引數

當你不確定函式會被傳入幾個引數時,可以用 *args 來打包所有額外的位置引數。args 在函式內會被當成「元組(tuple)」處理,你可以像操作一般元組一樣走訪它。

def add_all(*args):
    print(f"收到的引數:{args}")
    return sum(args)

print(add_all(1, 2, 3))         # 輸出 6,args = (1, 2, 3)
print(add_all(10, 20, 30, 40)) # 輸出 100,args = (10, 20, 30, 40)

星號 * 是關鍵,它告訴 Python「把剩下的引數全部打包起來」。變數名稱不一定要叫 args,例如 *numbers 也可以,但 *args 是社群慣例,讀到的人會立刻理解這個變數的用途。

**kwargs:接收任意數量的關鍵字引數

如果想把關鍵字引數(name=value)也一起打包,可以用 **kwargs。kwargs 在函式內會是「字典(dict)」,鍵是參數名稱,值是對應的數值。

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Taipei")

執行程式會輸出 name: Alice、age: 25、city: Taipei。*args 與 **kwargs 也可以同時使用,但順序必須是「位置參數 → *args → 關鍵字參數 → **kwargs」。這個語法在設計需要高度彈性的 API(例如裝飾器、框架的設定傳遞)時非常有用。

作用域:LEGB 規則

昨天的內容我們簡單提過變數有「區域」與「全域」的差別,實際上 Python 用一套稱為 LEGB 的規則來決定變數的可見範圍,由內到外依序為:Local(函式內)、Enclosing(外層函式)、Global(模組層級)、Built-in(Python 內建)。

x = "全域"

def outer():
    x = "外層函式"
    def inner():
        x = "內層函式"
        print(x)  # 輸出「內層函式」
    inner()
    print(x)      # 輸出「外層函式」

outer()
print(x)          # 輸出「全域」

同名的變數在不同的範圍可以各自獨立,這就是 LEGB 規則的作用。實務上,常見的作法是「避免與內建名稱衝突」,例如不要把自己的變數取名叫 list、dict、str 等,否則可能會覆蓋掉內建功能。除非真的需要修改全域變數,否則盡量讓函式透過參數與 return 與外界溝通,這樣可以避免很多難以追蹤的 bug。

lambda 匿名函式

有些函式邏輯非常簡單,只有一兩行,卻要為了它特地寫一個 def 區塊,這時可以用 lambda 表達式以做為替代。lambda 是一種匿名函式(anonymous function),語法是 lambda 參數: 運算式,會自動回傳運算式的結果。

# 一般寫法
def square(x):
    return x ** 2

# lambda 寫法
square = lambda x: x ** 2

print(square(5))  # 輸出 25

# 常見搭配:sorted() 用 lambda 指定排序鍵
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]
students_sorted = sorted(students, key=lambda s: s[1])
print(students_sorted)  # [('Carol', 78), ('Alice', 85), ('Bob', 92)]

lambda 適合用在「邏輯簡單、只用一次」的場景,例如搭配 sorted()、map()、filter() 等內建函式使用。但若邏輯較複雜、需要多行程式碼或註解,仍然建議使用一般的 def 函式,可讀性會好很多。

遞迴:自己呼叫自己

遞迴(recursion)是一種特殊的技巧:函式在內部「呼叫自己」。它特別適合用於「問題可以拆成性質相同但規模更小的子問題」的情境,例如階乘、費氏數列、樹狀結構走訪等。

# 計算 n 的階乘:n! = n * (n-1) * ... * 1
def factorial(n):
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # 輸出 120

遞迴函式一定要有「終止條件」,否則會無限呼叫自己直到超出最大遞迴深度而報錯。上例的 if n == 0 or n == 1: return 1 就是終止條件。除了階乘,遞迴也能用來實作費氏數列:

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print([fibonacci(i) for i in range(10)])
# 輸出 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

遞迴程式碼簡潔優雅,但對大型輸入可能會有效能或遞迴深度問題(Python 預設遞迴深度約 1000 層)。實務上若需要處理大量資料,通常會改用迴圈或加入「記憶化(memoization)」快取重複計算的結果。

常見錯誤與除錯建議

寫函式時,有幾個常見的錯誤值得留意。熟悉這些坑點能讓你在除錯時更快找到問題所在。

忘記 return:如果函式內沒有寫 return,預設會回傳 None,呼叫端的結果就會是 None 而不是預期的值。

預設參數使用串列或字典:Python 的預設參數是在「函式定義時」就建立的,因此每次呼叫共用同一個物件,容易踩到 mutable default argument 的陷阱。建議改用 None 當預設值,函式內再建立新物件。

# 錯誤示範
def add_item(item, items=[]):
    items.append(item)
    return items

# 正確示範
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

遞迴沒寫終止條件:忘了寫終止條件會導致 RecursionError。寫遞迴時,建議先把「最小問題」的答案寫在最前面,再處理一般情況。

函式當作參數傳遞

在 Python 中,函式本身也是一個「物件」,可以當作參數傳給別的函式,也能從函式回傳另一個函式。這種「函式優先級(first-class function)」的特性,是函式型程式設計的基礎,也是 map()、filter() 等內建函式的運作原理。

def apply_twice(func, value):
    return func(func(value))

def double(x):
    return x * 2

print(apply_twice(double, 3))  # 輸出 12(先 double(3)=6,再 double(6)=12)

這種把行為當參數傳遞的做法,能讓程式碼更靈活。例如同一個排序函式,可以透過傳入不同的比較函式來改變排序規則;同一個篩選函式,可以透過傳入不同的條件函式來過濾不同資料。在學習 Pandas、scikit-learn 等套件時,你也會大量看到這種「把函式傳給函式」的寫法。

裝飾器初探

「函式可以當參數、也可以被函式回傳」這兩個特性組合起來,就能寫出「裝飾器(decorator)」。裝飾器是一種「接收函式、回傳新函式」的函式,常用來在不修改原本程式碼的前提下,額外加上記錄、除錯、權限檢查等功能。

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"呼叫 {func.__name__},引數:{args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"回傳值:{result}")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

print(add(3, 5))

執行後會看到三行輸出:「呼叫 add」、「回傳值:8」,以及最後的「8」。@logger 是裝飾器的語法糖,等同於 add = logger(add)。裝飾器在網頁框架、測試工具與深度學習框架中都非常常見,理解這個概念會讓你在閱讀他人程式碼時更順暢。雖然本章只是「初探」,但完整的裝飾器教學會在後續進階章節展開。

小結:函式進階的學習路徑

本章介紹了不少工具,乍看有些凌亂,但其實它們圍繞著同一個主題——「讓函式更靈活」。*args 與 **kwargs 解決的是「輸入的彈性」;LEGB 規則解決的是「變數的可見性」;lambda 與遞迴解決的是「函式設計的多種風格」;把函式當作參數、裝飾器初探則是「函式組合」的可能性。

建議讀者寫完本章後,可以用「學到一個新工具就重構一段舊程式碼」的方式練習。例如把 Day 6 的猜數字遊戲中的部分邏輯抽出成函式,用 *args 讓它能接受多個難度等級等。透過這樣的練習,你會更深刻體會到這些技巧的實用價值。

結語

今天我們認識了 Python 函式的進階主題:*args 與 **kwargs 提供了極高的參數彈性;LEGB 規則讓我們能清楚掌握變數的作用域;lambda 適合用在簡短邏輯;遞迴則提供了一種拆解問題的思維方式。掌握這些技巧後,你已經能寫出更具彈性、更模組化的程式碼。

明天,我們會把焦點轉到字串處理。字串是程式裡最常操作的資料之一,從切割、合併到正規表達式,這些工具會讓你在處理文字資料時更得心應手。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...