2026年10月12日 星期一

Python Closure 閉包:5 個簡單又經典的範例

PYTHON 入門|函式如何記住資料

Python Closure 閉包:5 個簡單又經典的範例

Closure(閉包)讓內層函式在外層函式結束後,仍能記住並使用外層函式建立的資料。它常用來保存狀態、建立可設定的函式,也能幫助我們理解裝飾器的運作方式。

簡單記法:閉包通常包含外層函式、內層函式,以及內層函式所記住的外層變數。

一、閉包的運作流程

外層函式建立資料和內層函式,接著把內層函式回傳給外部。外層函式結束後,回傳的函式仍保有它需要的外層資料。

呼叫外層函式make_greeter("小明") 建立外層資料name = "小明" 回傳內層函式greet 記住 name 之後呼叫 greet() 外層函式已結束,閉包仍保留所需資料
閉包把內層函式與它需要的外層變數關聯起來,因此外層函式結束後仍可使用。

二、範例 1:閉包記住名字

def make_greeter(name):
    def greet():
        print(f"你好,{name}!")
    return greet


hello = make_greeter("小明")
hello()

執行結果

你好,小明!

程式解說

  1. 呼叫 make_greeter("小明") 時,外層函式的參數 name 取得「小明」。
  2. 內層函式 greet() 使用了外層的 name。
  3. return greet 回傳函式本身,注意這裡沒有加括號;若寫成 return greet(),就會立刻執行它,而不是回傳函式。
  4. 回傳的函式存入 hello。之後呼叫 hello(),它仍能讀取閉包保留的名字。

三、範例 2:建立不同的問候函式

def make_greeter(name):
    def greet():
        print(f"你好,{name}!")
    return greet


greet_ming = make_greeter("小明")
greet_hua = make_greeter("小華")

greet_ming()
greet_hua()

執行結果

你好,小明!
你好,小華!

程式解說

  • 第一次呼叫 make_greeter,建立一個記住「小明」的閉包。
  • 第二次呼叫時,建立另一個記住「小華」的閉包。
  • greet_ming 和 greet_hua 是不同的函式物件,各自保存自己的 name。
  • 因此呼叫其中一個,不會改變另一個閉包記住的資料。

四、範例 3:建立會累加的計數器

def make_counter():
    count = 0

    def counter():
        nonlocal count
        count += 1
        return count

    return counter


counter = make_counter()

print(counter())
print(counter())
print(counter())

執行結果

1
2
3

程式解說

  • make_counter() 把 count 初始化為 0,並定義內層函式 counter()。
  • 每次呼叫內層函式,count += 1 就把原本記住的值加一,再回傳新值。
  • nonlocal count 告訴 Python:這裡要修改最近一層外部函式中的 count,而不是建立新的區域變數。
  • 如果移除 nonlocal,因為程式在函式內對 count 賦值,Python 會把它視為區域變數;在讀取它之前尚未設定值,會發生 UnboundLocalError。

五、範例 4:各自保存起始值的計數器

def make_counter(start):
    count = start

    def counter():
        nonlocal count
        count += 1
        return count

    return counter


counter_a = make_counter(0)
counter_b = make_counter(10)

print(counter_a())
print(counter_a())
print(counter_b())

執行結果

1
2
11

程式解說

  • 這個版本讓建立計數器時指定起始值,count = start 把起始值放進閉包。
  • counter_a 記住自己的 count = 0;counter_b 記住自己的 count = 10。
  • 呼叫 counter_a() 兩次,得到 1、2;呼叫 counter_b() 一次,則從 10 加到 11。
  • 每次執行 make_counter() 都會建立一組新的外層環境,所以兩個計數器的狀態互不干擾。

六、範例 5:建立簡單的乘法器

def make_multiplier(number):
    def multiply(value):
        return number * value
    return multiply


double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))
print(triple(5))

執行結果

10
15

程式解說

  • make_multiplier(2) 回傳一個記住 number = 2 的函式,存入 double。
  • make_multiplier(3) 回傳另一個記住 number = 3 的函式,存入 triple。
  • 呼叫 double(5) 時,內層函式把 2 與 5 相乘;呼叫 triple(5) 時,則把 3 與 5 相乘。
  • 這種寫法可以建立「已預先設定部分資料」的函式。

七、閉包重點整理

  • 內層函式使用了外層函式的變數,並且被保留下來,就形成閉包。
  • 閉包可以在外層函式結束後,繼續使用外層變數。
  • 每次呼叫外層函式都可以建立一個有獨立狀態的新閉包。
  • 要在內層函式修改外層函式的變數,使用 nonlocal。
  • 閉包常用於保存狀態、建立函式工廠、設定回呼行為,以及實作裝飾器。

學習閉包時,可以先追蹤三件事:外層函式建立了什麼資料、內層函式使用了哪些資料,以及外層函式結束後是哪個回傳函式仍保有這些資料。掌握這個關係,就能看懂許多 Python 程式中的閉包寫法。

沒有留言:

張貼留言