On this page
Scope & Closure
ตัวแปรอยู่ "ที่ไหน" และฟังก์ชันจำค่าจาก scope แม่ได้อย่างไร — รากฐานของ decorator
scope คือ "ขอบเขตที่ตัวแปรมองเห็นได้" การเข้าใจว่า Python ค้นหาตัวแปรอย่างไรช่วยให้ไม่งงกับบั๊กแปลก ๆ และเป็นพื้นฐานของ closure ซึ่งต่อยอดไปสู่ decorator ในหัวข้อถัดไป
กฎ LEGB — Python หาตัวแปรจากไหน
เมื่อใช้ตัวแปร Python ค้นหาตามลำดับ Local → Enclosing → Global → Built-in เจอที่ไหนใช้ที่นั่น
| ตัวอักษร | ขอบเขต | คือ |
|---|---|---|
| L | Local | ในฟังก์ชันปัจจุบัน |
| E | Enclosing | ฟังก์ชันแม่ที่ห่อหุ้มอยู่ |
| G | Global | ระดับไฟล์/โมดูล |
| B | Built-in | ของ Python เอง (print, len, ...) |
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local (เจอ L ก่อน)
inner()
print(x) # enclosing
outer()
print(x) # globalglobal และ nonlocal
ปกติการกำหนดค่าในฟังก์ชันสร้างตัวแปร local ใหม่ ถ้าต้องการแก้ตัวแปรชั้นนอกจริง ๆ ต้องประกาศ global (ระดับไฟล์) หรือ nonlocal (ฟังก์ชันแม่)
count = 0
def increment():
global count # บอกว่าจะแก้ count ตัวข้างนอก
count += 1
increment()
increment()
print(count) # 2
def make_counter():
n = 0
def step():
nonlocal n # แก้ n ของฟังก์ชันแม่ (ไม่ใช่สร้างใหม่)
n += 1
return n
return stepclosure: ฟังก์ชันที่จำค่าจาก scope แม่
closure เกิดเมื่อฟังก์ชันชั้นในใช้ตัวแปรจากฟังก์ชันแม่ แล้วฟังก์ชันแม่คืนฟังก์ชันชั้นในออกมา — ฟังก์ชันชั้นในจะ "จำ" ตัวแปรนั้นไว้แม้ฟังก์ชันแม่จบไปแล้ว
def make_multiplier(factor):
def multiply(x):
return x * factor # จำ factor จากแม่ไว้
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10)) # 20 (จำ factor=2)
print(triple(10)) # 30 (จำ factor=3)double กับ triple คือฟังก์ชันคนละตัว แต่ละตัวจำ factor ของตัวเองไว้ — นี่คือ closure
counter ด้วย closure
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3 (จำ count ไว้ข้ามการเรียก)ถ้าสร้าง closure ใน for-loop โดยอ้างตัวแปรลูป ทุก closure จะจับ "ตัวแปรเดียวกัน" ซึ่งมีค่าสุดท้ายหลัง loop จบ ไม่ใช่ค่าตอนสร้าง วิธีแก้: ส่งค่าผ่าน default argument เช่น lambda x, i=i: ... เพื่อ "แช่แข็ง" ค่า i ตอนนั้น
# ❌ ทุก f จะคืน 2 (ค่าสุดท้ายของ i)
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2]
# ✅ แช่แข็งค่าด้วย default argument
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
print([f() for f in funcs]) # [0, 1, 2]สรุปหัวข้อนี้
- LEGB: Python หาตัวแปร Local → Enclosing → Global → Built-in
- global แก้ตัวแปรระดับไฟล์, nonlocal แก้ตัวแปรฟังก์ชันแม่
- closure = ฟังก์ชันชั้นในที่จำค่าจาก scope แม่แม้แม่จบไปแล้ว
- ระวัง late binding ใน loop — แช่แข็งค่าด้วย default argument
1) เขียน make_adder(n) ที่คืนฟังก์ชันบวก n 2) เขียน counter ที่นับขึ้นเรื่อย ๆ ด้วย closure 3) ทดลองสร้าง lambda ใน loop แบบผิด แล้วแก้ให้ถูกด้วย default argument 4) อธิบายว่าทำไม global count จำเป็นในฟังก์ชัน increment