Làm cách nào để thay đổi một biến mô-đun từ một mô-đun khác?


106

Giả sử tôi có một gói được đặt tên barvà nó chứa bar.py:

a = None

def foobar():
    print a

__init__.py:

from bar import a, foobar

Sau đó, tôi thực thi tập lệnh này:

import bar

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

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

None
1
1

Đâ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?

Câu trả lời:


103

Bạn đang sử dụng from bar import a. atrở 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 xảy ra trong).

Khi bạn chỉ định một giá trị mới a, bạn cũng chỉ thay đổi ađiểm giá trị nào chứ không phải giá trị thực. Cố gắng nhập bar.pytrực tiếp với import bartrong __init__.pyvà tiến hành thử nghiệm của bạn ở đó bằng cách cài đặt bar.a = 1. Bằng cách này, bạn sẽ thực sự sửa đổi bar.__dict__['a']giá trị 'thực' atrong ngữ cảnh này.

Nó hơi phức tạp với ba lớp nhưng bar.a = 1thay đổi giá trị của atrong mô-đun được gọi barlà thực sự bắt nguồn từ __init__.py. Nó không thay đổi giá trị của acái đó foobarvì nó foobarnằm trong tệp thực bar.py. Bạn có thể đặt bar.bar.anếu bạn muốn thay đổi điều đó.

Đây là một trong những mối nguy hiểm của việc sử dụng from foo import barbiểu mẫu của importcâu lệnh: nó chia barthành hai biểu tượng, một biểu tượng có thể nhìn thấy trên toàn cầu từ bên trong foobắt đầu trỏ đến giá trị ban đầu và một biểu tượng khác hiển thị trong phạm vi mà importcâu lệnh được thực thi. Thay đổi vị trí mà một biểu tượng trỏ đến cũng không thay đổi giá trị mà nó trỏ đến.

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


Cảm ơn! Câu trả lời của bạn có lẽ chỉ giúp tôi tiết kiệm vài giờ tìm thủ phạm.
jnns 13/02/19

Có vẻ như ngay cả bar.bar.aphương pháp này cũng không giúp được nhiều trong hầu hết các trường hợp sử dụng. Có thể là một ý tưởng yếu khi kết hợp các bài tập biên dịch hoặc tải mô-đun và thời gian chạy bởi vì bạn sẽ khiến chính mình (hoặc những người khác sử dụng mã của bạn) nhầm lẫn. Đặc biệt là trong các ngôn ngữ cư xử hơi mâu thuẫn về nó, như python được cho là có.
matanster

26

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 barnhập khẩu hoặc bar/__init__.pyhoặ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.

Đây là cách nó làm việc:

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

from bar import a

trên thực tế, điều gì đó giống như

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

và xác định một biến mới ( bar/__init__.py:a, nếu bạn muốn). Vì vậy, bạn from bar import a__init__.pyvới phím tắt tên bar/__init__.py:avới bản gốc bar.py:ađối tượng ( None). Đây là lý do tại sao bạn có thể làm from bar import a as a2trong __init__.py: trong trường hợp này, rõ ràng là bạn có cả hai bar/bar.py:avà một tên biến riêng biệtbar/__init__.py:a2 (trong trường hợp của bạn, tên của hai biến chỉ xảy ra với cả hai a, nhưng chúng vẫn tồn tại trong các không gian tên khác nhau: in __init__.py, chúng 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(vì import barnhập của bạn bar/__init__.py). Đâ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 bar/bar.py:a. Vì vậy, khi bạn sau đó làm

bar.foobar()

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

Kết luận: tốt nhất là tránh bất kỳ sự mơ hồ nào trong đó import bar, bằng cách không có bất kỳ bar/bar.pymô-đun nào (vì đã bar.__init__.pytạo thư mục bar/thành một gói nên bạn cũng có thể nhập với import bar).


12

Nói một cách khác: Hóa ra quan niệm sai lầm này rất dễ thực hiện. Nó được định nghĩa một cách lén lút trong tham chiếu ngôn ngữ Python: việc sử dụng đối tượng thay vì biểu tượng . Tôi đề nghị rằng tham chiếu ngôn ngữ Python làm cho điều này rõ ràng hơn và ít thưa thớt hơn ..

Biểu frommẫu không ràng buộc tên mô-đun: nó đi qua danh sách các định danh, tìm kiếm từng định danh trong mô-đun được tìm thấy ở bước (1) và liên kết tên trong không gian tên cục bộ với đối tượng được tìm thấy như vậy.

TUY NHIÊN:

Khi bạn nhập, bạn nhập giá trị hiện tại của ký hiệu đã nhập và thêm nó vào không gian tên của bạn như đã xác định. Bạn không nhập một tham chiếu, bạn đang nhập một giá trị một cách hiệu quả.

Do đó, để nhận được giá trị cập nhật của i, bạn phải nhập một biến chứa tham chiếu đến biểu tượng đó.

Nói cách khác, nhập KHÔNG giống như khai báo trong importJAVA, externaltrong C / C ++ hoặc thậm chí là một usemệnh đề trong PERL.

Thay vào đó, câu lệnh sau trong Python:

from some_other_module import a as x

nhiều như đoạn mã sau trong K & R C:

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

int x = a;

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


Trên thực tế, tôi thấy cách của Python importgọn gàng hơn nhiều so với Java, bởi vì các không gian tên / phạm vi sẽ luôn được giữ riêng biệt và không bao giờ can thiệp vào nhau theo những cách không mong muốn. Giống như đây: Việc thay đổi liên kết của một đối tượng với một tên trong không gian tên (đọc: gán một thứ gì đó vào thuộc tính toàn cục 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, v.v. Trong Python, bạn chỉ cần hiểu những gì được nhập, trong khi trong Java, bạn cũng phải hiểu mô-đun khác trong trường hợp, nó thay đổi giá trị được nhập này sau này.
Tino

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

4
Khỉ thật, tôi nhấn trả lại… có một kỳ vọng nhập bằng tham chiếu (theo @OP). Trên thực tế, một lập trình viên Python mới, cho dù có kinh nghiệm đến đâu, cũng phải nói điều này theo cách "nhìn ra". Điều đó sẽ không bao giờ xảy ra: dựa trên cách sử dụng phổ biến, Python đã đi sai đường. Tạo "giá trị nhập" nếu cần, nhưng không kết hợp các ký hiệu với giá trị tại thời điểm nhập.
Mark Gerolimatos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.