iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0

因為之後想要探討 python 的 decorator,所以今天想先來探討一下它背後的原理,Python 的函示運作以及一個概念 "Closure"(閉包)。

首先要先簡單介紹一下 Python 的 函式

Functions in Python

1. 普通函式 (Regular Functions)
這是 Python 中最基本的函式形式,接收參數並返回值。

def hello(name):
    return f"Hello, {name}!"
print(hello("Yusinz"))  # Output: Hello, Yusinz!

2. 函式作為變數被傳遞 (First-Class Functions)
函式在 python中是一級公民,也被稱為 First-class function,它可以做為變數去傳遞,甚至作為其他函式的參數或返回值。

def hello(name):
    return (f"Hello, {name}!")

say_hi = hello
print(say_hi("Yusinz"))  # # Output: Hello, Yusinz!

3. 函式可以作為參數 (Function as Arguments)
函式可以被傳遞給另一個函式,這也是 closure 和 decorator 可以實現的重要概念。

def call_func(func):
    return func("Yusinz")

def hello(name):
    return (f"Hello, {name}!")

print(call_func(hello))  # Output: Hello, Yusinz!

4. 函式可以返回函式 (Function Returning Functions)
一個函式可以返回另一個函式。而外部函式返回內部函式正是閉包的核心。

def outer_function():
    def inner_function():
        return "Hello!, Yusinz!"
    return inner_function

hello = outer_function()
print(hello())  # Output: Hello, Yusinz!

5. 作用域和變數的可見性 (Scope and Variable Visibility)
區域變數或是全域變數大家應該耳熟能詳,就不多作介紹。

  • enclosing Scope
    作用域:在函式內部的函式中。
    可見性:內部函式可以訪問外部函式的變數。

  • Built-in Scope
    作用域:Python 的內建名稱和函式,如 print()len() 等。
    可見性:在整個 Module 中都可見,即使在其他作用域內部也可以訪問。

global_name = "Global Yusinz"

def hello():
    enclosing_name = "Enclosing Yusinz"
    def say_hello():
        local_name = "Local Yusinz" 
        print(f"Hello {global_name}!") #Output: Hello Global Yusinz!
        print(f"Hello {enclosing_name}!") #Output: Hello Local Yusinz!
        print(f"Hello {local_name}!") #Output: Hello Enclosing Yusinz!
    say_hello()

hello()

了解 Python 函式概念後,
理解這些基本概念後,就能快速的理解閉包( Closure ),因為閉包正是利用了函式作為一級公民的特性,並且依賴於作用域的概念來保存外部函式的變數。

What is Closure

閉包(closure)是指當外層的函式把一個內層的函式返回時,它會「記住」它所在的外層函式的變數,即使外層函式已經執行完畢。這讓內層函式能在外層函式結束後,繼續使用外部函式中的變數。

def outer_function():
    name = "Yusinz"
    def inner_function():
        print(f"Hello, {name}!")
    return inner_function

closure_example = outer_function()
closure_example()  # Output: Hello, Yusinz!
  • 外層函式 (outer_function) 宣告了 name = "Yusinz"
  • 內層函式 (inner_function) 被宣告在外層函式裡面,並使用了外層函式的變數 name。

當我們 call closure_example() 時,outer_function 會返回 inner_function,照理來說我們返回的 inner_function 裡面的 name 在執行完 outer_function 後生命週期就結束,我們單純要 closure_example() 去 call inner_function() 他應該讀不到外層的 name 才對,但實際執行後會發現,他還是可以印出 Hello, Yusinz!
這是因為返回的 inner_function 是一個 closure,它可以記憶住 name 的值。所以就算 outer_function 已經執行完畢了,closure 仍然可以使用 name

這個被記憶住的變數 name,我們可以叫他 captured variable,使用 captured variable 的函式 inner_functionclosure_example 就是 closure。

captured variable

captured variable 在使用時,如果需要賦值,會出現 UnboundLocalError,這是因為賦值之後 captured variable 會被轉換為區域變數,這時候它就會拿不到外層函式的變數。但要解決也很簡單使用 nonlocal 宣告變數後就能對它做操作了。

以 Counter 計數器為例

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

counter = create_counter()
print(counter())  # Output: 1
print(counter())  # Output: 2

你可能會想,那 closure 可以很多個嗎?
答案是可以,每個 closure 都是獨立的,以 counter 為例,可以看到 second_counter() 的值還會是 1。

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

counter = create_counter()
second_counter = create_counter()
print(counter())  # Output: 1
print(counter())  # Output: 2
print(counter())  # Output: 3
print(second_counter())  # Output: 1

最後,一樣建議大家可以實際去操作,才能更了解其中的用法,這裡一樣附上可以線上練習的 place

Reference


上一篇
Day-26 | Python - Pydantic
下一篇
Day-28 | Python - Decorator 裝飾器
系列文
埋藏在後端工程下的地雷與寶藏30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言