Điều gì sẽ xảy ra nếu hai mô-đun nhập khẩu lẫn nhau?
Để khái quát vấn đề, còn việc nhập theo chu kỳ trong Python thì sao?
Điều gì sẽ xảy ra nếu hai mô-đun nhập khẩu lẫn nhau?
Để khái quát vấn đề, còn việc nhập theo chu kỳ trong Python thì sao?
Câu trả lời:
Có một cuộc thảo luận thực sự tốt về vấn đề này tại comp.lang.python năm ngoái. Nó trả lời câu hỏi của bạn khá kỹ lưỡng.
Nhập khẩu là khá đơn giản thực sự. Chỉ cần nhớ những điều sau đây:
'Nhập' và 'từ xxx nhập yyy' là các câu lệnh thực thi. Họ thực thi khi chương trình đang chạy đến dòng đó.
Nếu một mô-đun không có trong sys.modules, thì việc nhập sẽ tạo ra mục nhập mô-đun mới trong sys.modules và sau đó thực thi mã trong mô-đun. Nó không trả lại điều khiển cho mô-đun gọi cho đến khi thực hiện xong.
Nếu một mô-đun tồn tại trong sys.modules thì việc nhập chỉ đơn giản trả về mô-đun đó cho dù nó có hoàn thành việc thực thi hay không. Đó là lý do tại sao nhập khẩu theo chu kỳ có thể trả về các mô-đun dường như trống một phần.
Cuối cùng, tập lệnh thực thi chạy trong một mô-đun có tên __main__, nhập tập lệnh dưới tên riêng của nó sẽ tạo ra một mô-đun mới không liên quan đến __main__.
Kết hợp nhiều thứ đó lại với nhau và bạn sẽ không gặp phải bất ngờ nào khi nhập mô-đun.
Nếu bạn làm import foo
bên trong bar
và import bar
bên trong foo
, nó sẽ hoạt động tốt. Vào thời điểm mọi thứ thực sự chạy, cả hai mô-đun sẽ được tải đầy đủ và sẽ có các tham chiếu cho nhau.
Vấn đề là khi thay vì bạn làm from foo import abc
và from bar import xyz
. Bởi vì bây giờ mỗi mô-đun yêu cầu mô-đun khác đã được nhập (để tên chúng tôi đang nhập tồn tại) trước khi có thể được nhập.
from foo import *
và from bar import *
cũng sẽ làm việc tốt.
from x import y
, nhưng vẫn nhận được lỗi nhập vòng tròn
import
câu lệnh được thực thi. Vì vậy, nó sẽ không lỗi nhưng bạn có thể không nhận được tất cả các biến bạn mong đợi.
from foo import *
và from bar import *
, mọi thứ được thực hiện trong foo
giai đoạn khởi tạo bar
và các chức năng thực tế trong bar
vẫn chưa được xác định ...
Nhập theo chu kỳ chấm dứt, nhưng bạn cần cẩn thận không sử dụng các mô-đun nhập theo chu kỳ trong quá trình khởi tạo mô-đun.
Hãy xem xét các tệp sau:
a.py:
print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"
b.py:
print "b in"
import a
print "b out"
x = 3
Nếu bạn thực hiện a.py, bạn sẽ nhận được những điều sau đây:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
Trong lần nhập thứ hai của b.py (trong lần thứ hai a in
), trình thông dịch Python không nhập b
lại, vì nó đã tồn tại trong mô-đun chính tả.
Nếu bạn cố gắng truy cập b.x
từ a
trong mô-đun khởi tạo, bạn sẽ nhận được một AttributeError
.
Nối dòng sau vào a.py
:
print b.x
Sau đó, đầu ra là:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
File "a.py", line 4, in <module>
import b
File "/home/shlomme/tmp/x/b.py", line 2, in <module>
import a
File "/home/shlomme/tmp/x/a.py", line 7, in <module>
print b.x
AttributeError: 'module' object has no attribute 'x'
Điều này là do các mô-đun được thực thi khi nhập và tại thời điểm b.x
được truy cập, dòng x = 3
chưa được thực thi, điều này sẽ chỉ xảy ra sau đó b out
.
__name__
thay vì 'a'
. Lúc đầu, tôi hoàn toàn bối rối tại sao một tập tin sẽ được thực thi hai lần.
Như các câu trả lời khác mô tả mô hình này được chấp nhận ở python:
def dostuff(self):
from foo import bar
...
Điều này sẽ tránh việc thực thi câu lệnh nhập khi tệp được nhập bởi các mô-đun khác. Chỉ khi có một phụ thuộc vòng tròn logic, điều này sẽ thất bại.
Hầu hết các Nhập khẩu Thông tư không thực sự nhập khẩu vòng tròn logic mà chỉ gây ra ImportError
lỗi, do cách import()
đánh giá các báo cáo cấp cao nhất của toàn bộ tệp khi được gọi.
Những thứ này ImportErrors
hầu như luôn có thể tránh được nếu bạn tích cực muốn hàng nhập khẩu của mình lên hàng đầu :
Xem xét nhập khẩu tròn này:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# images/serializers.py
from profiles.serializers import SimplifiedProfileSerializer
class SimplifiedImageSerializer(serializers.Serializer):
title = serializers.CharField()
class ImageSerializer(SimplifiedImageSerializer):
profile = SimplifiedProfileSerializer()
Từ David Beazley Các mô-đun và gói nói chuyện tuyệt vời : Sống và để chết! - PyCon 2015 , 1:54:00
đây là một cách để đối phó với nhập khẩu tròn trong python:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
Điều này cố gắng nhập SimplifiedImageSerializer
và nếu ImportError
được nâng lên, vì nó đã được nhập, nó sẽ kéo nó từ bộ nhập.
Tái bút: Bạn phải đọc toàn bộ bài viết này bằng giọng của David Beazley.
Tôi có một ví dụ ở đây mà đánh tôi!
foo.py
import bar
class gX(object):
g = 10
bar.py
from foo import gX
o = gX()
chính
import foo
import bar
print "all done"
Tại dòng lệnh: $ python main.py
Traceback (most recent call last):
File "m.py", line 1, in <module>
import foo
File "/home/xolve/foo.py", line 1, in <module>
import bar
File "/home/xolve/bar.py", line 1, in <module>
from foo import gX
ImportError: cannot import name gX
import bar
trong foo.py
đến cuối
bar
và foo
cả hai phải sử dụng gX
, giải pháp 'sạch nhất' là đưa gX
vào một mô-đun khác và có cả hai foo
và bar
nhập mô-đun đó. (sạch nhất theo nghĩa là không có phụ thuộc ngữ nghĩa ẩn.)
bar
thậm chí không thể tìm thấy gX
trong foo. nhập khẩu tròn là tốt, nhưng nó gX
không được xác định khi nhập.
Mô-đun a.py:
import b
print("This is from module a")
Mô-đun b.py
import a
print("This is from module b")
Chạy "Module a" sẽ xuất ra:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Nó xuất ra 3 dòng này trong khi nó được cho là xuất vô hạn vì nhập vòng tròn. Điều gì xảy ra từng dòng trong khi chạy "Mô-đun a" được liệt kê ở đây:
import b
. vì vậy nó sẽ ghé thăm mô-đun bimport a
. vì vậy nó sẽ ghé thăm mô-đun aimport b
nhưng lưu ý rằng dòng này sẽ không được thực hiện nữa , bởi vì mọi tệp trong python thực thi một dòng nhập chỉ một lần, không quan trọng là nó được thực thi ở đâu hoặc khi nào. Vì vậy, nó sẽ vượt qua dòng tiếp theo và in "This is from module a"
."This is from module b"
"This is from module a"
và chương trình sẽ kết thúc.Tôi hoàn toàn đồng ý với câu trả lời của pythoneer ở đây. Nhưng tôi đã vấp phải một số mã bị lỗi khi nhập vòng tròn và gây ra sự cố khi cố gắng thêm các bài kiểm tra đơn vị. Vì vậy, để nhanh chóng vá nó mà không thay đổi mọi thứ bạn có thể giải quyết vấn đề bằng cách nhập động.
# Hack to import something without circular import issue
def load_module(name):
"""Load module using imp.find_module"""
names = name.split(".")
path = None
for name in names:
f, path, info = imp.find_module(name, path)
path = [path]
return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")
Một lần nữa, đây không phải là bản sửa lỗi vĩnh viễn nhưng có thể giúp người nào đó muốn sửa lỗi nhập mà không thay đổi quá nhiều mã.
Chúc mừng!
Có rất nhiều câu trả lời tuyệt vời ở đây. Mặc dù thường có các giải pháp nhanh chóng cho vấn đề, một số trong số đó cảm thấy pythonic hơn các giải pháp khác, nếu bạn có thể thực hiện tái cấu trúc, một cách tiếp cận khác là phân tích tổ chức mã của bạn và cố gắng loại bỏ sự phụ thuộc vòng tròn. Bạn có thể tìm thấy, ví dụ, bạn có:
Tập tin a.py
from b import B
class A:
@staticmethod
def save_result(result):
print('save the result')
@staticmethod
def do_something_a_ish(param):
A.save_result(A.use_param_like_a_would(param))
@staticmethod
def do_something_related_to_b(param):
B.do_something_b_ish(param)
Tập tin b.py
from a import A
class B:
@staticmethod
def do_something_b_ish(param):
A.save_result(B.use_param_like_b_would(param))
Trong trường hợp này, chỉ cần di chuyển một phương thức tĩnh sang một tệp riêng, giả sử c.py
:
Tập tin c.py
def save_result(result):
print('save the result')
sẽ cho phép xóa save_result
phương thức khỏi A và do đó cho phép xóa nhập A từ a trong b:
Tập tin tái cấu trúc a.py
from b import B
from c import save_result
class A:
@staticmethod
def do_something_a_ish(param):
A.save_result(A.use_param_like_a_would(param))
@staticmethod
def do_something_related_to_b(param):
B.do_something_b_ish(param)
Tập tin được cấu trúc lại
from c import save_result
class B:
@staticmethod
def do_something_b_ish(param):
save_result(B.use_param_like_b_would(param))
Tóm lại, nếu bạn có một công cụ (ví dụ như pylint hoặc PyCharm) báo cáo về các phương thức có thể tĩnh, chỉ cần ném một vật staticmethod
trang trí lên chúng có thể không phải là cách tốt nhất để tắt tiếng cảnh báo. Mặc dù phương thức có vẻ liên quan đến lớp, nhưng tốt hơn là nên tách nó ra, đặc biệt nếu bạn có một số mô-đun liên quan chặt chẽ có thể cần cùng chức năng và bạn có ý định thực hành các nguyên tắc DRY.
Nhập khẩu tròn có thể gây nhầm lẫn vì nhập khẩu có hai điều:
Cái trước chỉ được thực hiện một lần, trong khi cái sau ở mỗi câu lệnh nhập. Nhập khẩu tròn tạo ra tình huống khi nhập mô-đun sử dụng mô-đun nhập với mã được thực thi một phần. Do đó, nó sẽ không nhìn thấy các đối tượng được tạo sau câu lệnh nhập. Dưới đây mẫu mã chứng minh điều đó.
Nhập khẩu thông tư không phải là cái ác cuối cùng cần phải tránh bằng mọi giá. Trong một số khung như Flask, chúng khá tự nhiên và điều chỉnh mã của bạn để loại bỏ chúng không làm cho mã tốt hơn.
chính
print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
print 'imports done'
print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)
b.by
print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"
a.py
print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"
đầu ra chính của python với ý kiến
import b
b in, __name__ = b # b code execution started
b imports a
a in, __name__ = a # a code execution started
a imports b # b code execution is already in progress
b has x True
b has y False # b defines y after a import,
a out
b out
a in globals() False # import only adds a to main global symbol table
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available
Tôi đã giải quyết vấn đề theo cách sau, và nó hoạt động tốt mà không có lỗi. Hãy xem xét hai tập tin a.py
và b.py
.
Tôi đã thêm nó vào a.py
và nó hoạt động.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
Đầu ra tôi nhận được là
>>> b out
>>> a out
>>> 5
Ok, tôi nghĩ rằng tôi có một giải pháp khá tuyệt vời. Giả sử bạn có tập tin a
và tập tin b
. Bạn có một def
hoặc một class
trong tập tin b
mà bạn muốn sử dụng trong mô-đun a
, nhưng bạn có cái gì khác, hoặc một def
, class
hoặc biến từ tập tin a
mà bạn cần trong định nghĩa hoặc lớp học của bạn trong tập tin b
. Những gì bạn có thể làm là, ở dưới cùng của tệp a
, sau khi gọi hàm hoặc lớp trong tệp a
cần trong tệp b
, nhưng trước khi gọi hàm hoặc lớp từ tệp b
mà bạn cần cho tệp a
, hãy nói import b
Sau đó, và đây là phần chính , trong tất cả các định nghĩa hoặc các lớp trong tệp b
cần def
hoặcclass
từ tệpa
(hãy gọi nó CLASS
), bạn nóifrom a import CLASS
Điều này hoạt động vì bạn có thể nhập tệp b
mà không cần Python thực hiện bất kỳ câu lệnh nhập nào trong tệp b
và do đó bạn bỏ qua mọi nhập khẩu tròn.
Ví dụ:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
class B(object):
def __init__(self, number):
self.number = number
def dostuff(self):
from a import CLASS
print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."
Voila.
from a import CLASS
không thực sự bỏ qua việc thực thi tất cả các mã trong a.py. Đây là những gì thực sự xảy ra: (1) Tất cả mã trong a.py được chạy dưới dạng một mô-đun đặc biệt "__main__". (2) Tại import b
, mã cấp cao nhất trong b.py được chạy (xác định lớp B) và sau đó kiểm soát trả về "__main__". (3) "__main__" cuối cùng vượt qua sự kiểm soát go.dostuff()
. (4) khi do do () đến import a
, nó sẽ chạy lại tất cả mã trong a.py , lần này là mô-đun "a"; sau đó nó nhập đối tượng LỚP từ mô-đun mới "a". Vì vậy, trên thực tế, điều này sẽ hoạt động tốt như nhau nếu bạn sử dụng import a
bất cứ nơi nào trong b.py.