Bỏ qua để đến nội dung

Late binding closures trong vòng lặp

# Tạo list các functions
functions = []
for i in range(5):
functions.append(lambda: i)
# Gọi từng function
for func in functions:
print(func())

Bạn nghĩ sẽ in: 0 1 2 3 4

Kết quả thực tế: 4 4 4 4 4

What the Python?! Tất cả functions đều return 4?! 😱

Python closures sử dụng late binding: giá trị của biến được lookup lúc function ĐƯỢC GỌI, không phải lúc được TẠO!

functions = []
for i in range(5):
# Lambda KHÔNG capture giá trị của i
# Lambda chỉ REFERENCE đến biến i
functions.append(lambda: i)
# Sau loop, i = 4
# Tất cả lambdas đều reference đến CÙNG biến i
# Khi gọi, i đã = 4 → Tất cả return 4
# Sau khi loop kết thúc
print(i) # 4
# Mọi lambda đều reference biến i này
functions[0]() # Lookup i → 4
functions[1]() # Lookup i → 4
functions[2]() # Lookup i → 4

✅ Solution 1: Default argument (Khuyến khích!)

Phần tiêu đề “✅ Solution 1: Default argument (Khuyến khích!)”
functions = []
for i in range(5):
# Default argument được evaluate LÚC TẠO
functions.append(lambda x=i: x) # Capture giá trị của i
# In ra đúng
for func in functions:
print(func()) # 0 1 2 3 4 ✅
from functools import partial
def return_value(x):
return x
functions = []
for i in range(5):
functions.append(partial(return_value, i))
for func in functions:
print(func()) # 0 1 2 3 4 ✅
def make_func(x):
return lambda: x # x là local variable của make_func
functions = []
for i in range(5):
functions.append(make_func(i))
for func in functions:
print(func()) # 0 1 2 3 4 ✅
# List comprehension tạo scope riêng
functions = [lambda x=i: x for i in range(5)]
for func in functions:
print(func()) # 0 1 2 3 4 ✅
# SAI
buttons = []
for i in range(5):
# Tất cả buttons đều gọi callback với i=4!
button = Button(onclick=lambda: print(f"Button {i}")) # ❌
buttons.append(button)
# ĐÚNG
buttons = []
for i in range(5):
button = Button(onclick=lambda x=i: print(f"Button {x}")) # ✅
buttons.append(button)
import tkinter as tk
# SAI
for i in range(5):
btn = tk.Button(
root,
text=str(i),
command=lambda: print(i) # ❌ Tất cả print 4
)
btn.pack()
# ĐÚNG
for i in range(5):
btn = tk.Button(
root,
text=str(i),
command=lambda x=i: print(x) # ✅
)
btn.pack()
import asyncio
# SAI
async def main():
tasks = []
for i in range(5):
# Tất cả tasks đều dùng i=4
tasks.append(asyncio.create_task(process(lambda: i))) # ❌
# ĐÚNG
async def main():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(process(lambda x=i: x))) # ✅
# SAI
functions = []
for i in range(3):
for j in range(3):
functions.append(lambda: (i, j)) # ❌
for func in functions:
print(func()) # Tất cả print (2, 2)
# ĐÚNG
functions = []
for i in range(3):
for j in range(3):
functions.append(lambda x=i, y=j: (x, y)) # ✅
for func in functions:
print(func()) # (0,0), (0,1), (0,2), (1,0), ...
# For loop - SHARED scope
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2] ❌
# Python 3: List comp có scope riêng
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2] ❌ Vẫn late binding!
# Phải dùng default arg
funcs = [lambda x=i: x for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2] ✅

Closures trong Python dùng LATE BINDING

  • lambda: i - Reference biến i
  • lambda x=i: x - Capture giá trị i
  • 🐛 Bug phổ biến với loops + lambdas
  • 🔧 Luôn dùng default argument để capture values

Pattern an toàn:

for i in range(n):
func = lambda x=i: do_something(x) # Capture i