gpt4 book ai didi

python - Python 中的循环模块依赖和相对导入

转载 作者:IT老高 更新时间:2023-10-28 21:58:31 28 4
gpt4 key购买 nike

假设我们有两个具有循环依赖关系的模块:

# a.py
import b
def f(): return b.y
x = 42
# b.py
import a
def g(): return a.x
y = 43

这两个模块在目录 pkg中空的 __init__.py .进口 pkg.apkg.b工作正常,如 this answer 中所述.如果我将进口更改为相对进口
from . import b

我得到一个 ImportError尝试导入其中一个模块时:
>>> import pkg.a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pkg/a.py", line 1, in <module>
from . import b
File "pkg/b.py", line 1, in <module>
from . import a
ImportError: cannot import name a

为什么会出现此错误?情况不是和上面差不多吗? (这与 this question有关吗?)

编辑 : 这个问题与软件设计无关。我知道避免循环依赖的方法,但无论如何我对错误的原因很感兴趣。

最佳答案

首先让我们从如何开始from import在 python 中工作:

那么首先让我们看一下字节码:

>>> def foo():
... from foo import bar

>>> dis.dis(foo)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('bar',))
6 IMPORT_NAME 0 (foo)
9 IMPORT_FROM 1 (bar)
12 STORE_FAST 0 (bar)
15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE

嗯很有趣:),所以 from foo import bar翻译成第一个 IMPORT_NAME foo相当于 import foo然后 IMPORT_FROM bar .

现在是什么 IMPORT_FROM 做 ?

让我们看看python找到时做了什么 IMPORT_FROM :
TARGET(IMPORT_FROM)
w = GETITEM(names, oparg);
v = TOP();
READ_TIMESTAMP(intr0);
x = import_from(v, w);
READ_TIMESTAMP(intr1);
PUSH(x);
if (x != NULL) DISPATCH();
break;

嗯,基本上他得到了要从中导入的名称,这在我们的 foo() 中。函数将是 bar ,然后他从帧堆栈中弹出值 v这是最后一个执行的操作码的返回 IMPORT_NAME ,然后调用函数 import_from()用这两个参数:
static PyObject *
import_from(PyObject *v, PyObject *name)
{
PyObject *x;

x = PyObject_GetAttr(v, name);

if (x == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Format(PyExc_ImportError, "cannot import name %S", name);
}
return x;
}

如您所见 import_from()功能很简单,先尝试获取属性 name来自模块 v ,如果它不存在,它会引发 ImportError否则返回此属性。

现在这与相对导入有什么关系?

以及相关的导入,如 from . import b例如,在 OP 问题中的情况下等同于 from pkg import b .

但这是怎么回事?要理解这一点,我们应该看看 import.c python的模块专门针对函数 get_parent() .如您所见,该函数很长,无法在此处列出,但一般而言,当它看到相对导入时,它会尝试替换点 .与父包取决于 __main__模块,再次来自 OP 问题是包 pkg .

现在让我们把所有这些放在一起,并试图弄清楚为什么我们最终会出现 OP 问题中的行为。

为此,如果我们可以在执行导入时看到 python 做什么,这将对我们有所帮助,这是我们的幸运日,python 已经具有此功能,可以通过在超详细模式下运行来启用它 -vv .

所以使用命令行: python -vv -c 'import pkg.b' :
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

import pkg # directory pkg
# trying pkg/__init__.so
# trying pkg/__init__module.so
# trying pkg/__init__.py
# pkg/__init__.pyc matches pkg/__init__.py
import pkg # precompiled from pkg/__init__.pyc
# trying pkg/b.so
# trying pkg/bmodule.so
# trying pkg/b.py
# pkg/b.pyc matches pkg/b.py
import pkg.b # precompiled from pkg/b.pyc
# trying pkg/a.so
# trying pkg/amodule.so
# trying pkg/a.py
# pkg/a.pyc matches pkg/a.py
import pkg.a # precompiled from pkg/a.pyc
# clear[2] __name__
# clear[2] __file__
# clear[2] __package__
# clear[2] __name__
# clear[2] __file__
# clear[2] __package__
...
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "pkg/b.py", line 1, in <module>
from . import a
File "pkg/a.py", line 2, in <module>
from . import a
ImportError: cannot import name a
# clear __builtin__._

嗯,在 ImportError 之前发生了什么?

第一) from . import apkg/b.py被调用,如上面解释的那样翻译为 from pkg import a , 再次在字节码中等同于 import pkg; getattr(pkg, 'a') .但是等一下 a也是模块吗?!
如果我们有类似 from module|package import module 的东西,那么有趣的部分来了。在这种情况下,将发生第二次导入,即导入子句中模块的导入。所以再次在 OP 示例中,我们现在需要导入 pkg/a.py ,正如您所知,我们首先在 sys.modules 中设置了我们新模块的 key 是 pkg.a然后我们继续我们对模块 pkg/a.py的解释, 但在模块之前 pkg/a.py完成导入调用 from . import b .

现在来 第二)部分, pkg/b.py将被导入,反过来它将首先尝试 import pkg因为 pkg已经导入所以有一个 key pkg在我们的 sys.modules它只会返回该键的值。然后它会 import b设置 pkg.b键入 sys.modules并开始解释。我们到达这条线 from . import a !

但是记住 pkg/a.py已经导入,这意味着 ('pkg.a' in sys.modules) == True所以导入将被跳过,只有 getattr(pkg, 'a')会被调用,但是会发生什么? python 未完成导入 pkg/a.py !?所以只有 getattr(pkg, 'a')将被调用,这将引发 AttributeErrorimport_from()函数,将被转换为 ImportError(cannot import name a) .

免责声明 :这是我自己努力了解解释器内部发生的事情,我离成为专家还很远。

编辑:这个答案被改写了,因为当我再次尝试阅读它时,我注意到我的答案是如何表述错误的,希望现在它会更有用:)

关于python - Python 中的循环模块依赖和相对导入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6351805/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com