Xét rằng Python là một ngôn ngữ "giải thích", một cách tự nhiên giả định rằng nếu một biến đã được xác định trong một phạm vi ngoài cùng biến vẫn có thể truy cập trong phạm vi bên trong. Và thực sự trong ví dụ đầu tiên của bạn khi bên trong _func
chỉ in x
nó hoạt động.
Điều không rõ ràng về Python mặc dù đây không phải là cách xác định phạm vi của một biến. Phân tích Python tại thời điểm biên dịch thời gian biến nào được coi là "cục bộ" thành phạm vi dựa trên việc chúng có được gán cho phạm vi đó hay không. Trong trường hợp này, nhiệm vụ bao gồm các toán tử gán tăng cường. Vì vậy, khi Python biên dịch bên trong của bạn _func
trong ví dụ thứ hai để bytecode nó thấy x += 1
và xác định rằng x
phải là một biến địa phương để _func
. Ngoại trừ các khóa học kể từ khi nhiệm vụ đầu tiên của nó là một bài tập tăng cường không có x
biến tại địa phương để tăng thêm và bạn nhận được UnboundLocalError
.
Một cách khác nhau để xem điều này có thể được để viết một cái gì đó như:
def _func():
print x
x = 2
đây một lần nữa vì _func
chứa dòng x = 2
nó sẽ đối xử với x
như là một biến địa phương trong phạm vi chức năng và không như x
được xác định trong hàm ngoài. Vì vậy, print x
cũng sẽ dẫn đến một số UnboundLocalError
.
Bạn có thể kiểm tra này một cách chi tiết sâu hơn bằng cách sử dụng các mô-đun dis
để hiển thị bytecode tạo ra cho các chức năng:
>>> dis.dis(_func)
2 0 LOAD_FAST 0 (x)
3 PRINT_ITEM
4 PRINT_NEWLINE
3 5 LOAD_CONST 1 (2)
8 STORE_FAST 0 (x)
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
Các opcode LOAD_FAST
được cho dành cho tra cứu "nhanh" của các biến địa phương mà bỏ qua chậm hơn, tra cứu tên tổng quát hơn. Nó sử dụng một mảng con trỏ trong đó mỗi biến cục bộ (trong khung ngăn xếp hiện tại) được kết hợp với một chỉ mục trong mảng đó, thay vì đi qua tra cứu từ điển và như vậy. Trong ví dụ trên, đối số duy nhất cho mã vạch LOAD_FAST
là 0
- trong trường hợp này là địa chỉ đầu tiên (và duy nhất).
Bạn có thể kiểm tra về chức năng riêng của mình (đặc biệt là đối tượng mã cơ bản của nó) rằng có một biến địa phương sử dụng trong mã đó, và rằng tên biến liên kết với nó là 'x'
:
>>> _func.__code__.co_nlocals
1
>>> _func.__code__.co_varnames
('x',)
Đó là cách dis.dis
có thể báo cáo 0 LOAD_FAST 0 (x)
. Tương tự như vậy đối với opcode STORE_FAST
sau này.
Không cần biết điều này để hiểu các phạm vi biến đổi trong Python, nhưng có thể hữu ích khi biết những gì đang xảy ra dưới mui xe.
Như đã đề cập trong một số câu trả lời khác, Python 3 đã giới thiệu từ khóa nonlocal
ngăn chặn việc liên kết biên dịch tên này thành biến cục bộ dựa trên gán cho biến đó trong phạm vi cục bộ.
Đây là lý do tại sao python 3 thêm 'nonlocal' – FatalError
Kiểm tra điều này: http://stackoverflow.com/questions/5218895/python-nested-functions-variable-scoping – Germano
Tôi thường sử dụng một đối tượng để lưu trữ ngữ cảnh vì bạn có thể sửa đổi các thuộc tính của đối tượng trong phần đóng. Ví dụ: 'x = {'val': 0}' rồi 'x ['val'] + = 1' bên trong' _func'. – six8