Điều này liên quan đến cách Python dịch mã của bạn sang bytecode (bước biên dịch).
Khi biên dịch một hàm, Python xử lý tất cả các biến được gán dưới dạng biến cục bộ và thực hiện tối ưu hóa để giảm số lượng tra cứu tên mà nó sẽ phải làm. Mỗi biến cục bộ được gán một chỉ mục và khi hàm được gọi là giá trị của chúng sẽ được lưu trữ trong một mảng cục bộ ngăn xếp được chỉ mục theo chỉ mục. Trình biên dịch sẽ phát ra mã vạch LOAD_FAST
và STORE_FAST
để truy cập biến.
Cú pháp global
chỉ thay cho trình biên dịch ngay cả khi biến được gán giá trị, nó không được coi là biến cục bộ, không được gán chỉ mục. Thay vào đó, nó sẽ sử dụng opcode LOAD_GLOBAL
và STORE_GLOBAL
để truy cập biến. Những opcode chậm hơn kể từ khi họ sử dụng tên để làm một tra cứu trong có thể nhiều từ điển (người dân địa phương, globals).
Nếu biến chỉ được truy cập để đọc giá trị, trình biên dịch luôn phát ra LOAD_GLOBAL
vì nó không biết liệu biến đó có phải là biến cục bộ hay toàn cục hay không, và do đó giả sử nó là toàn cầu.Vì vậy, trong chức năng đầu tiên của bạn, sử dụng global x
thông báo cho trình biên dịch mà bạn muốn nó xử lý quyền truy cập ghi vào x
làm văn bản cho biến toàn cầu thay vì biến cục bộ. Opcodes cho chức năng làm cho nó rõ ràng:
>>> dis.dis(changeXto1)
3 0 LOAD_CONST 1 (1)
3 STORE_GLOBAL 0 (x)
6 LOAD_CONST 0 (None)
9 RETURN_VALUE
Trong ví dụ thứ ba của bạn, bạn nhập các module __main__
vào một biến địa phương tên là __main__
và sau đó gán cho lĩnh vực x
của nó. Vì mô-đun là đối tượng lưu trữ tất cả các ánh xạ cấp cao nhất làm trường, bạn đang gán cho biến x
trong mô-đun __main__
. Và như bạn thấy, các trường mô-đun __main__
trực tiếp ánh xạ tới các giá trị trong từ điển globals()
vì mã của bạn được xác định trong mô-đun __main__
. Opcodes chứng minh rằng bạn không truy cập trực tiếp x
:
>>> dis.dis(changeXto3)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (None)
6 IMPORT_NAME 0 (__main__)
9 STORE_FAST 0 (__main__)
3 12 LOAD_CONST 2 (3)
15 LOAD_FAST 0 (__main__)
18 STORE_ATTR 1 (x)
21 LOAD_CONST 0 (None)
24 RETURN_VALUE
Ví dụ thứ hai là thú vị. Vì bạn gán một giá trị cho biến số x
, trình biên dịch giả sử nó là một biến cục bộ và thực hiện tối ưu hóa. Sau đó, from __main__ import x
sẽ nhập mô-đun __main__
và tạo liên kết mới giá trị x
trong mô-đun __main__
với biến cục bộ có tên x
. Điều này luôn xảy ra, from ${module} import ${name}
chỉ cần tạo một ràng buộc mới cho không gian tên hiện tại. Khi bạn gán giá trị mới cho biến x
bạn chỉ cần thay đổi ràng buộc hiện tại, không phải ràng buộc trong mô-đun __main__
không liên quan (mặc dù nếu giá trị là có thể thay đổi được và bạn thay đổi nó, thay đổi sẽ hiển thị qua tất cả các ràng buộc). Dưới đây là các opcodes:
>>> dis.dis(f2)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('x',))
6 IMPORT_NAME 0 (__main__)
9 IMPORT_FROM 1 (x)
12 STORE_FAST 0 (x)
15 POP_TOP
3 16 LOAD_CONST 3 (2)
19 STORE_FAST 0 (x)
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
Một cách tốt để suy nghĩ về việc này là bằng Python tất cả các nhiệm vụ có tính bắt buộc tên cho một giá trị trong một cuốn từ điển, và dereference chỉ được thực hiện một tra cứu từ điển (đây là một xấp xỉ thô , nhưng khá gần với mô hình khái niệm). Khi thực hiện obj.field
, khi đó bạn đang tìm kiếm từ điển ẩn của obj
(có thể truy cập qua obj.__dict__
) cho khóa "field"
.
Khi bạn có một tên biến thường, sau đó nó được tra cứu trong từ điển locals()
, sau đó từ điển globals()
nếu nó khác nhau (chúng giống nhau khi mã được thực thi ở cấp mô-đun). Đối với một nhiệm vụ, nó luôn luôn đặt ràng buộc trong từ điển locals()
, trừ khi bạn tuyên bố rằng bạn muốn truy cập toàn cầu bằng cách thực hiện global ${name}
(cú pháp này cũng hoạt động ở cấp cao nhất).
Vì vậy, dịch chức năng của bạn, điều này gần như là nếu bạn đã viết:
# NOTE: this is valid Python code, but is less optimal than
# the original code. It is here only for demonstration.
def changeXto1():
globals()['x'] = 1
def changeXto2():
locals()['x'] = __import__('__main__').__dict__['x']
locals()['x'] = 2
def changeXto3():
locals()['__main__'] = __import__('__main__')
locals()['__main__'].__dict__['x'] = 3
changeXto2 chỉ đặt một biến địa phương: http://www.saltycrane.com/blog/2008/01/python-variable- phạm vi ghi chú/ –
Tôi không thấy bất kỳ điều gì về việc nhập mô-đunnhập vào đó. –