Phạm vi biến
Bạn sẽ học được
- Quy tắc LEGB: local → enclosing → global → builtin.
- Vì sao gán vào biến toàn cục trong hàm gây
UnboundLocalError. - Vì sao nên tránh
global. nonlocalvà closure.- Phạm vi của biến trong comprehension và việc che builtin.
Khái niệm cốt lõi
Python tìm tên theo thứ tự LEGB:
| Chữ | Phạm vi | Ví dụ |
|---|---|---|
| Local | Trong hàm hiện tại | Tham số, biến gán trong hàm |
| Enclosing | Hàm bao ngoài | Biến của hàm chứa hàm lồng |
| Global | Cấp module/file | Biến gán ngoài mọi hàm |
| Builtin | Tên có sẵn | print, len, sum |
Tìm theo thứ tự, dừng ở nơi đầu tiên có tên. Hiểu quy tắc này giải thích gần hết các lỗi phạm vi.
count = 0
def tang():
count = 5
print("trong hàm:", count)
tang()
print("ngoài hàm:", count)trong hàm: 5
ngoài hàm: 0count = 5 trong hàm tạo biến cục bộ mới, không sửa biến toàn cục. Hai biến trùng tên nhưng khác phạm vi.
UnboundLocalError — cái bẫy lớn
Nếu vừa đọc vừa gán cùng một tên trong hàm, Python coi tên đó là cục bộ trong toàn bộ hàm:
count = 0
def tang_sai():
count += 1
tang_sai()UnboundLocalError: cannot access local variable 'count' where it is not associated with a valuecount += 1 tương đương count = count + 1: đọc count trước khi gán. Vì có phép gán ở đâu đó trong hàm, Python xếp count vào local, và lúc đọc thì local chưa có giá trị. Biến toàn cục count = 0 không được dùng.
Đây là lỗi rất khó hiểu nếu không biết quy tắc: biến toàn cục rõ ràng tồn tại, nhưng hàm vẫn báo chưa gán.
global — dùng hạn chế
Khai báo global buộc tên trỏ về phạm vi module:
count = 0
def tang():
global count
count += 1
tang()
tang()
print(count)2Chạy đúng, nhưng đây là thiết kế nên tránh:
- Hàm phụ thuộc trạng thái bên ngoài, khó kiểm thử độc lập.
- Gọi hàm hai lần cho kết quả khác nhau — khó suy luận.
- Nhiều hàm cùng sửa một biến toàn cục tạo bug khó truy vết.
- Trong đa luồng, biến toàn cục cần đồng bộ.
Cách tốt hơn là truyền vào và trả ra:
def tang(count):
return count + 1
count = 0
count = tang(count)
count = tang(count)
print(count)2Hàm thuần (chỉ phụ thuộc tham số, chỉ trả kết quả) dễ kiểm thử và tái sử dụng. global chỉ nên xuất hiện trong script nhỏ hoặc khi thật sự cần trạng thái module.
nonlocal và closure
Hàm lồng nhau có thể đọc biến của hàm ngoài. Muốn gán vào biến đó, cần nonlocal:
def outer():
total = 0
def inner(x):
nonlocal total
total += x
return total
print(inner(5))
print(inner(3))
return total
print(outer())5
8
8nonlocal total nói rằng total thuộc hàm bao ngoài, không phải local của inner. Không có nonlocal, dòng total += x sẽ gây UnboundLocalError giống trường hợp global.
Closure hữu ích khi cần state riêng cho từng thực thể:
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c(), c(), c())1 2 3Mỗi lần gọi make_counter() tạo một count riêng. Hai bộ đếm độc lập nhau — điều mà biến toàn cục không làm được.
Phạm vi trong comprehension
Từ Python 3, biến trong comprehension có phạm vi riêng, không rò ra ngoài:
vals = [x for x in range(3)]
print(vals)
print(x)[0, 1, 2]
NameError: name 'x' is not definedĐây là thay đổi so với Python 2 và là lý do không thể dùng biến vòng lặp sau comprehension. Nếu cần giá trị cuối, gán tường minh trong vòng lặp thường.
Che builtin
Đặt tên biến trùng builtin sẽ che nó trong phạm vi hiện tại:
list = [1, 2, 3]
print(list)
# print(list("abc")) # TypeError: 'list' object is not callableSau khi gán list, hàm list() không còn gọi được trong phạm vi đó. Tương tự với sum, max, input, id, type. Tránh đặt tên biến là list, dict, sum, max, min, input, file, type. Nếu lỡ che, del list hoặc khởi động lại kernel/REPL.
Thực hành
- Gán
count = 5trong hàm và kiểm tra biến ngoài không đổi. - Tái hiện
UnboundLocalErrorvớicount += 1, đọc thông báo. - Sửa bằng
global, sau đó sửa lại bằng truyền tham số và trả kết quả. - Viết closure đếm bằng
nonlocal, tạo hai bộ đếm độc lập. - Dùng comprehension rồi thử in biến vòng lặp để thấy
NameError. - Đặt
list = [1, 2], thử gọilist("abc"), rồi khôi phục.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
UnboundLocalError | Vừa đọc vừa gán trong hàm | global/nonlocal hoặc truyền tham số |
| Biến ngoài không đổi | Hàm tạo biến local trùng tên | Trả giá trị và gán lại |
NameError sau comprehension | Comprehension có phạm vi riêng | Gán kết quả hoặc dùng vòng lặp |
TypeError: 'list' object is not callable | Che builtin list | Đổi tên biến, del list |
| Closure không giữ state | Thiếu nonlocal | Thêm nonlocal |
| Hai bộ đếm dùng chung state | Dùng biến toàn cục | Dùng closure/class |
global gây bug khó tái hiện | Trạng thái chia sẻ | Thiết kế hàm thuần |
Tóm tắt
- Tên tìm theo LEGB; gán trong hàm tạo biến local.
- Vừa đọc vừa gán cùng tên →
UnboundLocalError. globalchạy được nhưng nên tránh; truyền tham số và trả kết quả tốt hơn.nonlocalcho closure ghi vào biến của hàm ngoài.- Comprehension có phạm vi riêng; đừng đặt tên biến trùng builtin.
Dùng ← → để chuyển bài