2010-08-21 23 views
51

Giả sử tôi có một gói có tên bar, và nó chứa bar.py:Làm thế nào để thay đổi một biến mô-đun từ mô-đun khác?

a = None 

def foobar(): 
    print a 

__init__.py:

from bar import a, foobar 

Sau đó, tôi thực hiện kịch bản này:

import bar 

print bar.a 
bar.a = 1 
print bar.a 
bar.foobar() 

Dưới đây là những gì tôi mong đợi:

None 
1 
1 

Dưới đây là những gì tôi nhận được:

None 
1 
None 

bất cứ ai có thể giải thích quan niệm sai lầm của tôi?

Trả lời

47

bạn đang sử dụng from bar import a. a trở thành một biểu tượng trong phạm vi toàn cầu của mô-đun nhập (hoặc bất kỳ phạm vi nào mà câu lệnh nhập xuất hiện). Vì vậy, khi bạn chỉ định giá trị mới cho a, bạn chỉ cần thay đổi giá trị a điểm nào, không phải giá trị thực tế. hãy nhập trực tiếp bar.py bằng import bar trong __init__.py và tiến hành thử nghiệm của bạn tại đó bằng cách đặt bar.a = 1. Bằng cách này, bạn đang thực sự sửa đổi bar.__dict__['a'] là giá trị 'thực' của a trong ngữ cảnh này.

Đó là một chút phức tạp với ba lớp nhưng bar.a = 1 thay đổi giá trị a trong mô-đun được gọi là bar thực sự được lấy từ __init__.py. Nó không thay đổi giá trị của a rằng foobar thấy vì foobar sống trong tệp thực tế bar.py. Bạn có thể đặt bar.bar.a nếu bạn muốn thay đổi điều đó.

Đây là một trong những nguy hiểm của việc sử dụng các hình thức from foo import bar của báo cáo kết quả import: nó chia tách bar thành hai biểu tượng, một có thể nhìn thấy trên toàn cầu từ bên trong foo mà bắt đầu tắt trỏ đến giá trị ban đầu và một biểu tượng khác nhau có thể nhìn thấy trong phạm vi nơi tuyên bố import được thực hiện. Thay đổi nơi một điểm biểu tượng không thay đổi giá trị mà nó chỉ ra.

Loại công cụ này là kẻ giết người khi cố gắng reload mô-đun từ trình thông dịch tương tác.

14

Một nguồn khó khăn với câu hỏi này là bạn có một chương trình mang tên bar/bar.py: import bar nhập khẩu hoặc bar/__init__.py hoặc bar/bar.py, tùy thuộc vào nơi nó được thực hiện, mà làm cho nó một chút rườm rà để theo dõi mà abar.a.

Dưới đây là cách hoạt động:

Chìa khóa để hiểu biết những gì xảy ra là để nhận ra rằng trong bạn __init__.py,

from bar import a 

có hiệu lực làm điều gì đó như

a = bar.a # … with bar = bar/bar.py (as if bar were imported locally from __init__.py) 

và định nghĩa một biến mới (bar/__init__.py:a, nếu bạn muốn). Do đó, from bar import a của bạn trong __init__.py liên kết tên bar/__init__.py:a với đối tượng bar.py:a gốc (None). Đây là lý do bạn có thể làm from bar import a as a2 trong __init__.py: trong trường hợp này, rõ ràng là bạn có cả hai bar/bar.py:ariêng biệt tên biến bar/__init__.py:a2 (trong trường hợp của bạn, tên của hai biến chỉ xảy ra cho cả hai là a, nhưng chúng vẫn tồn tại ở các không gian tên khác nhau: trong __init__.py, chúng là bar.aa).

Bây giờ, khi bạn làm

import bar 

print bar.a 

bạn đang truy cập biến bar/__init__.py:a (kể từ import bar nhập khẩu bar/__init__.py của bạn). Đây là biến bạn sửa đổi (thành 1). Bạn không chạm vào nội dung của biến số bar/bar.py:a. Vì vậy, khi bạn sau đó làm

bar.foobar() 

bạn gọi bar/bar.py:foobar(), mà truy cập biến a từ bar/bar.py, mà vẫn còn None (khi foobar() được định nghĩa, nó liên kết với tên biến một lần và cho tất cả, vì vậy a trong bar.pybar.py:a, không phải bất kỳ biến số a được xác định trong một mô-đun khác — vì có thể có nhiều biến số a trong tất cả các mô-đun đã nhập). Do đó, đầu ra None cuối cùng.

5

Để đặt cách khác: Biến quan niệm sai lầm này rất dễ thực hiện. It is sneakily defined in the Python language reference: việc sử dụng đối tượng thay vì biểu tượng. Tôi sẽ đề nghị rằng các tài liệu tham khảo ngôn ngữ Python làm cho điều này rõ ràng hơn và ít thưa thớt ..

Dạng from không ràng buộc vào tên module: nó đi qua các danh sách các định danh, trông mỗi một trong số họ lên trong tìm thấy mô-đun trong bước (1) và liên kết tên trong không gian tên cục bộ với đối tượng do đó, tìm thấy .

TUY NHIÊN:

Khi bạn nhập, bạn nhập giá trị hiện tại của biểu tượng nhập khẩu và thêm nó vào không gian tên của bạn theo quy định.Bạn không nhập tham chiếu, bạn đang nhập một giá trị hiệu quả.

Do đó, để nhận giá trị cập nhật là i, bạn phải nhập biến có tham chiếu đến biểu tượng đó.

Nói cách khác, việc nhập KHÔNG giống như import trong JAVA, khai báo external trong C/C++ hoặc thậm chí một mệnh đề use trong PERL.

Thay vào đó, báo cáo kết quả sau bằng Python:

from some_other_module import a as x 

nhiều như đoạn mã sau vào K & RC:

extern int a; /* import from the EXTERN file */ 

int x = a; 

(caveat: trong trường hợp Python, "a" và "x" về cơ bản là tham chiếu đến giá trị thực tế: bạn không sao chép INT, bạn đang sao chép địa chỉ tham chiếu)

+0

Thực ra, tôi tìm thấy P Cách nhập '' của ython sạch hơn nhiều so với Java, bởi vì không gian tên/phạm vi sẽ luôn được giữ đúng cách và không bao giờ can thiệp lẫn nhau theo cách bất ngờ. Giống như ở đây: Thay đổi một ràng buộc của một đối tượng thành một tên trong một không gian tên (đọc: giả định một cái gì đó vào thuộc tính toàn cầu của mô-đun) không bao giờ ảnh hưởng đến các không gian tên khác (đọc: tham chiếu được nhập) trong Python. Nhưng làm như vậy trong Java, vv Trong Python bạn chỉ cần hiểu những gì được nhập khẩu, trong khi trong Java, bạn cũng phải hiểu các module khác trong trường hợp, nó làm thay đổi giá trị này sau này. – Tino

+0

Tôi phải không đồng ý khá vất vả. Việc nhập/bao gồm/sử dụng có một lịch sử lâu dài về việc sử dụng biểu mẫu tham chiếu chứ không phải là dạng giá trị trong sự ham muốn về mọi ngôn ngữ khác. –

+0

Chết tiệt, tôi nhấn trở lại ... có một kỳ vọng nhập khẩu theo tham chiếu (theo @OP). Trong thực tế, một lập trình viên Python mới, không có vấn đề làm thế nào có kinh nghiệm, đã được nói với điều này trong "nhìn ra" loại cách. Điều đó sẽ không bao giờ xảy ra: dựa trên cách sử dụng thông thường, Python đã đi sai đường dẫn. Tạo "giá trị nhập" nếu cần, nhưng không được ký hiệu với các giá trị tại thời điểm nhập. –

Các vấn đề liên quan