Xol*_*lve 318 python circular-dependency cyclic-reference
如果两个模块相互导入会发生什么?
为了概括这个问题,Python中的循环导入怎么样?
小智 270
如果你做import foo
内部bar
和import bar
内部foo
,它将工作正常.当任何实际运行时,两个模块将完全加载并且将相互引用.
问题是,当你不是做from foo import abc
和from bar import xyz
.因为现在每个模块都需要导入其他模块(以便我们导入的名称存在)才能导入.
Sha*_*son 262
去年在comp.lang.python上有一个非常好的讨论.它非常彻底地回答了你的问题.
进口非常简单.请记住以下内容:
'import'和'from xxx import yyy'是可执行语句.它们在正在运行的程序到达该行时执行.
如果模块不在sys.modules中,则导入会在sys.modules中创建新模块条目,然后执行模块中的代码.在执行完成之前,它不会将控制权返回给调用模块.
如果sys.modules中存在模块,则导入只会返回该模块,无论它是否已完成执行.这就是为什么循环导入可能会返回看似部分为空的模块的原因.
最后,执行脚本在名为__main__的模块中运行,以自己的名称导入脚本将创建一个与__main__无关的新模块.
把这些东西放在一起,导入模块时不应该有任何意外.
Tor*_*rek 98
循环导入终止,但在模块初始化期间需要注意不要使用循环导入的模块.
请考虑以下文件:
a.py:
print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"
Run Code Online (Sandbox Code Playgroud)
b.py:
print "b in"
import a
print "b out"
x = 3
Run Code Online (Sandbox Code Playgroud)
如果您执行a.py,您将获得以下内容:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
Run Code Online (Sandbox Code Playgroud)
在第二次导入b.py(在第二个中a in
)时,Python解释器不会b
再次导入,因为它已存在于模块dict中.
如果您尝试访问b.x
从a
模块的初始化过程中,你会得到一个AttributeError
.
将以下行附加到a.py
:
print b.x
Run Code Online (Sandbox Code Playgroud)
然后,输出是:
$ 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'
Run Code Online (Sandbox Code Playgroud)
这是因为模块是在导入时执行的,并且在b.x
访问时,该行x = 3
尚未执行,这只会在之后发生b out
.
Ite*_*iam 48
令我惊讶的是,还没有人提到由类型提示引起的循环导入。
\n如果仅由于类型提示而进行循环导入,则可以以干净的方式避免它们。
考虑main.py
哪个使用另一个文件中的异常:
from src.exceptions import SpecificException\n\nclass Foo:\n def __init__(self, attrib: int):\n self.attrib = attrib\n\nraise SpecificException(Foo(5))\n
Run Code Online (Sandbox Code Playgroud)\n以及专用的异常类exceptions.py
:
from src.main import Foo\n\nclass SpecificException(Exception):\n def __init__(self, cause: Foo):\n self.cause = cause\n\n def __str__(self):\n return f\'Expected 3 but got {self.cause.attrib}.\'\n
Run Code Online (Sandbox Code Playgroud)\n这将通过和提高ImportError
自main.py
导入exception.py
,反之亦然。Foo
SpecificException
因为Foo
仅在类型检查期间需要,所以我们可以使用输入模块中的常量exceptions.py
安全地使其导入有条件。该常量仅在类型检查期间有效,这允许我们有条件地导入,从而避免循环导入错误。\n需要注意的是,这样做不会在运行时在 excepts.py 中声明,这会导致. 为了避免这种情况,我们添加了将模块中的所有类型注释转换为字符串的功能。TYPE_CHECKING
True
Foo
Foo
NameError
from __future__ import annotations
因此,我们得到以下 Python 3.7+ 的代码:
\nfrom __future__ import annotations\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING: # Only imports the below statements during type checking\n \xe2\x80\x8bfrom src.main import Foo\n\nclass SpecificException(Exception):\n def __init__(self, cause: Foo): # Foo becomes \'Foo\' because of the future import\n \xe2\x80\x8bself.cause = cause\n\n \xe2\x80\x8bdef __str__(self):\n \xe2\x80\x8breturn f\'Expected 3 but got {self.cause.attrib}.\'\n
Run Code Online (Sandbox Code Playgroud)\n在 Python 3.6 中,future import 不存在,因此Foo
必须是字符串:
from typing import TYPE_CHECKING\nif TYPE_CHECKING: # Only imports the below statements during type checking\n \xe2\x80\x8bfrom src.main import Foo\n\nclass SpecificException(Exception):\n \xe2\x80\x8bdef __init__(self, cause: \'Foo\'): # Foo has to be a string\n \xe2\x80\x8bself.cause = cause\n\n \xe2\x80\x8bdef __str__(self):\n \xe2\x80\x8breturn f\'Expected 3 but got {self.cause.attrib}.\'\n
Run Code Online (Sandbox Code Playgroud)\n在 Python 3.5 及更低版本中,类型提示功能尚不存在。
\n在Python的未来版本中,注释功能可能会成为强制性的,之后将不再需要导入。这将发生在哪个版本中尚未确定。
这个答案基于Stefaan Lippens 提出的另一个解决方案,可以帮助你摆脱 Python 中的循环导入漏洞。
\nSeb*_*zny 25
正如其他答案所述,这种模式在python中是可以接受的:
def dostuff(self):
from foo import bar
...
Run Code Online (Sandbox Code Playgroud)
当其他模块导入文件时,这将避免执行import语句.只有存在逻辑循环依赖关系时,才会失败.
大多数循环导入实际上不是逻辑循环导入,而是引发ImportError
错误,因为import()
在调用时评估整个文件的顶级语句的方式.
ImportErrors
如果您肯定希望进口最重要,那么几乎总能避免这些:
考虑这个循环导入:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
Run Code Online (Sandbox Code Playgroud)
# images/serializers.py
from profiles.serializers import SimplifiedProfileSerializer
class SimplifiedImageSerializer(serializers.Serializer):
title = serializers.CharField()
class ImageSerializer(SimplifiedImageSerializer):
profile = SimplifiedProfileSerializer()
Run Code Online (Sandbox Code Playgroud)
来自David Beazleys的优秀演讲模块和套餐:Live and Let Die!- PyCon 2015,1:54:00
这是一种在python中处理循环导入的方法:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
Run Code Online (Sandbox Code Playgroud)
这会尝试导入SimplifiedImageSerializer
,如果ImportError
被引发,因为它已经被导入,它将从importcache中提取它.
PS:你必须以David Beazley的声音阅读这整篇文章.
我在这里得到了一个令我印象深刻的例子!
foo.py
import bar
class gX(object):
g = 10
Run Code Online (Sandbox Code Playgroud)
bar.py
from foo import gX
o = gX()
Run Code Online (Sandbox Code Playgroud)
main.py
import foo
import bar
print "all done"
Run Code Online (Sandbox Code Playgroud)
在命令行: $ 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
Run Code Online (Sandbox Code Playgroud)
模块a.py:
import b
print("This is from module a")
Run Code Online (Sandbox Code Playgroud)
模块b.py
import a
print("This is from module b")
Run Code Online (Sandbox Code Playgroud)
运行“模块a”将输出:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Run Code Online (Sandbox Code Playgroud)
它输出这3行,而由于循环导入而应该输出无穷大。下面列出了在运行“模块a”时逐行发生的情况:
import b
。因此它将访问模块bimport a
。因此它将访问模块aimport b
但是模块a的第一行是,但是请注意,该行将不再执行,因为python中的每个文件仅执行一次导入行,无论在何时何地执行都无关紧要。因此它将传递到下一行并打印"This is from module a"
。"This is from module b"
"This is from module a"
,程序将完成。这里有很多很棒的答案。虽然通常有快速解决问题的方法,其中一些感觉比其他人更 Pythonic,但如果您有幸进行一些重构,另一种方法是分析代码的组织,并尝试删除循环依赖。例如,您可能会发现:
文件 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)
Run Code Online (Sandbox Code Playgroud)
文件 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))
Run Code Online (Sandbox Code Playgroud)
在这种情况下,只需将一个静态方法移动到一个单独的文件,例如c.py
:
文件 c.py
def save_result(result):
print('save the result')
Run Code Online (Sandbox Code Playgroud)
将允许save_result
从 A 中删除方法,从而允许从 b 中的 a 中删除 A 的导入:
重构文件 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)
Run Code Online (Sandbox Code Playgroud)
重构文件 b.py
from c import save_result
class B:
@staticmethod
def do_something_b_ish(param):
save_result(B.use_param_like_b_would(param))
Run Code Online (Sandbox Code Playgroud)
总之,如果您有一个工具(例如 pylint 或 PyCharm)报告可以是静态的方法,那么仅仅staticmethod
在它们上抛出装饰器可能不是消除警告的最佳方法。尽管该方法似乎与类相关,但最好将其分开,特别是如果您有几个可能需要相同功能的密切相关的模块,并且您打算实践 DRY 原则。
归档时间: |
|
查看次数: |
125072 次 |
最近记录: |