ข้ามไปยังเนื้อหา

Closures

Python อนุญาตให้เรานิยาม function ไว้ภายในอีก function หนึ่งได้ function ตัวในสามารถเข้าถึงตัวแปรใน scope ของ function ตัวนอก (enclosing) ได้

def outer():
message = "Hello from outer"
def inner():
print(message) # accesses the enclosing scope
inner()
outer() # Hello from outer

เมื่อ function ตัวใน อ้างถึงตัวแปรจาก enclosing scope แล้วถูก return ออกไปสู่โลกภายนอก Python จะสร้าง closure ขึ้นมา function ตัวในนั้น “ปิดครอบ” (close over) ตัวแปรไว้ และทำให้ตัวแปรนั้นยังมีชีวิตอยู่แม้ outer จะทำงานเสร็จไปแล้ว

def make_greeter(greeting: str):
def greet(name: str) -> str:
return f"{greeting}, {name}!" # greeting is a free variable
return greet
hello = make_greeter("Hello")
hi = make_greeter("Hi")
print(hello("Alice")) # Hello, Alice!
print(hi("Bob")) # Hi, Bob!

greeting เป็น free variable ใน greet ไม่ได้ถูกนิยามภายใน greet และก็ไม่ใช่ global แต่อาศัยอยู่ใน local scope ของ make_greeter closure จะรักษาค่าของ greeting ให้มีชีวิตอยู่ตราบเท่าที่ hello หรือ hi ยังคงอยู่

เราสามารถตรวจสอบตัวแปรที่ closure จับไว้ได้ผ่าน __closure__:

print(hello.__closure__[0].cell_contents) # Hello

ภายใน nested function เราสามารถ อ่าน free variable ได้โดยไม่ต้องใช้ keyword พิเศษใด ๆ แต่ถ้าจะ กำหนดค่าใหม่ (reassign) ให้ตัวแปรนั้น เราต้องใช้ nonlocal มิฉะนั้น Python จะมองว่าชื่อนั้นเป็นตัวแปร local ตัวใหม่

def make_counter(start: int = 0):
count = start
def increment(step: int = 1) -> int:
nonlocal count # tells Python: reuse the enclosing 'count'
count += step
return count
def reset() -> None:
nonlocal count
count = start
return increment, reset
inc, rst = make_counter()
print(inc()) # 1
print(inc()) # 2
print(inc(5)) # 7
rst()
print(inc()) # 1

ถ้าไม่มี nonlocal count บรรทัด count += step จะทำให้เกิด UnboundLocalError เพราะ Python เห็นการ assign แล้วมองว่า count เป็นตัวแปร local ที่ยังไม่ได้ถูกกำหนดค่า

def make_validator(min_val: int, max_val: int):
"""Return a validator function for the given range."""
def validate(value: int) -> bool:
return min_val <= value <= max_val
validate.__doc__ = f"Return True if value is in [{min_val}, {max_val}]."
return validate
is_valid_age = make_validator(0, 120)
is_valid_score = make_validator(0, 100)
is_valid_port = make_validator(1, 65535)
tests = [
("age 25", is_valid_age(25)),
("age 200", is_valid_age(200)),
("score 95", is_valid_score(95)),
("score 101", is_valid_score(101)),
("port 8080", is_valid_port(8080)),
("port 0", is_valid_port(0)),
]
for label, result in tests:
print(f"{label}: {result}")
'closure' ใน Python คือ:
keyword ใดที่ต้องใช้เพื่อกำหนดค่าใหม่ (reassign) ให้ free variable จาก enclosing scope ภายใน nested function?
ทำไม `[lambda: i for i in range(3)]` ถึงให้ผลลัพธ์ `[2, 2, 2]` เมื่อถูกเรียก?