Để hiểu các phụ thuộc vòng tròn, bạn cần nhớ rằng Python về bản chất là một ngôn ngữ kịch bản. Việc thực thi các câu lệnh bên ngoài các phương thức xảy ra tại thời điểm biên dịch. Các câu lệnh nhập được thực thi giống như các lệnh gọi phương thức, và để hiểu chúng, bạn nên nghĩ về chúng giống như các lệnh gọi phương thức.
Khi bạn nhập, điều gì sẽ xảy ra phụ thuộc vào việc tệp bạn đang nhập đã tồn tại trong bảng mô-đun hay chưa. Nếu có, Python sử dụng bất cứ thứ gì hiện có trong bảng ký hiệu. Nếu không, Python sẽ bắt đầu đọc tệp mô-đun, biên dịch / thực thi / nhập bất cứ thứ gì nó tìm thấy ở đó. Các ký hiệu được tham chiếu tại thời điểm biên dịch có được tìm thấy hay không, tùy thuộc vào việc chúng đã được nhìn thấy hay chưa được trình biên dịch nhìn thấy.
Hãy tưởng tượng bạn có hai tệp nguồn:
Tệp X.py
def X1:
return "x1"
from Y import Y2
def X2:
return "x2"
Tệp Y.py
def Y1:
return "y1"
from X import X1
def Y2:
return "y2"
Bây giờ, giả sử bạn biên dịch tệp X.py. Trình biên dịch bắt đầu bằng cách xác định phương thức X1, sau đó nhấn câu lệnh nhập trong X.py. Điều này khiến trình biên dịch tạm dừng biên dịch X.py và bắt đầu biên dịch Y.py. Ngay sau đó trình biên dịch truy cập vào câu lệnh nhập trong Y.py. Vì X.py đã có trong bảng mô-đun, Python sử dụng bảng ký hiệu X.py chưa hoàn chỉnh hiện có để đáp ứng mọi tham chiếu được yêu cầu. Bất kỳ ký hiệu nào xuất hiện trước câu lệnh nhập trong X.py hiện đều có trong bảng ký hiệu, nhưng bất kỳ ký hiệu nào sau đó thì không. Vì X1 bây giờ xuất hiện trước câu lệnh nhập nên nó đã được nhập thành công. Python sau đó tiếp tục biên dịch Y.py. Khi làm như vậy, nó xác định Y2 và kết thúc quá trình biên dịch Y.py. Sau đó, nó tiếp tục biên dịch X.py và tìm thấy Y2 trong bảng ký hiệu Y.py. Quá trình biên dịch cuối cùng hoàn thành lỗi w / o.
Một điều gì đó rất khác sẽ xảy ra nếu bạn cố gắng biên dịch Y.py từ dòng lệnh. Trong khi biên dịch Y.py, trình biên dịch truy cập câu lệnh nhập trước khi nó định nghĩa Y2. Sau đó, nó bắt đầu biên dịch X.py. Ngay sau đó nó chạm vào câu lệnh nhập trong X.py yêu cầu Y2. Nhưng Y2 là không xác định, vì vậy biên dịch không thành công.
Xin lưu ý rằng nếu bạn sửa đổi X.py để nhập Y1, quá trình biên dịch sẽ luôn thành công, bất kể bạn biên dịch tệp nào. Tuy nhiên, nếu bạn sửa đổi tệp Y.py để nhập ký hiệu X2, thì cả tệp sẽ không biên dịch.
Bất kỳ lúc nào khi mô-đun X hoặc bất kỳ mô-đun nào được nhập bởi X có thể nhập mô-đun hiện tại, KHÔNG sử dụng:
from X import Y
Bất kỳ lúc nào bạn nghĩ rằng có thể có một lần nhập vòng tròn, bạn cũng nên tránh biên dịch tham chiếu thời gian đến các biến trong các mô-đun khác. Hãy xem xét mã trông có vẻ ngây thơ:
import X
z = X.Y
Giả sử mô-đun X nhập mô-đun này trước khi mô-đun này nhập X. Ngoài ra, giả sử Y được định nghĩa trong X sau câu lệnh nhập. Sau đó, Y sẽ không được xác định khi mô-đun này được nhập và bạn sẽ gặp lỗi biên dịch. Nếu mô-đun này nhập Y trước, bạn có thể bỏ qua nó. Nhưng khi một trong những đồng nghiệp của bạn thay đổi thứ tự các định nghĩa trong mô-đun thứ ba một cách vô tội vạ, thì mã sẽ bị hỏng.
Trong một số trường hợp, bạn có thể giải quyết sự phụ thuộc vòng tròn bằng cách di chuyển câu lệnh nhập xuống bên dưới các định nghĩa ký hiệu mà các mô-đun khác cần. Trong các ví dụ trên, các định nghĩa trước câu lệnh nhập không bao giờ bị lỗi. Các định nghĩa sau câu lệnh nhập đôi khi không thành công, tùy thuộc vào thứ tự biên dịch. Bạn thậm chí có thể đặt các câu lệnh nhập ở cuối tệp, miễn là không cần ký hiệu đã nhập nào trong thời gian biên dịch.
Lưu ý rằng việc di chuyển các câu lệnh nhập xuống trong một mô-đun sẽ che khuất những gì bạn đang làm. Hãy bù đắp cho điều này bằng một nhận xét ở đầu mô-đun của bạn như sau:
#import X (actual import moved down to avoid circular dependency)
Nói chung đây là một tập tục xấu, nhưng đôi khi rất khó tránh.