Python 中的循环模块依赖关系和相对导入

发布于 2024-12-03 11:22:50 字数 1048 浏览 1 评论 0原文

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

# 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 工作正常,如 这个答案。如果我将导入更改为相对导入,

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

为什么会出现此错误?情况是不是和上面的情况差不多呢? (这是否与 这个问题?)

编辑:我知道避免循环依赖的方法,但无论如何我对错误的原因感兴趣。

Suppose we have two modules with cyclic dependencies:

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

The two modules are in the directory pkg with an empty __init__.py. Importing pkg.a or pkg.b works fine, as explained in this answer. If I change the imports to relative imports

from . import b

I get an ImportError when trying to import one of the modules:

>>> 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

Why do I get this error? Isn't the situation pretty much the same as above? (Is this related to this question?)

Edit: I'm aware of ways to avoid the circular dependency, but I'm interested in the reason for the error anyway.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

妄想挽回 2024-12-10 11:22:50

首先让我们从 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 相当于导入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() 函数非常简单,它首先尝试从模块 v 获取属性 name,如果它不存在则引发 ImportError 否则返回此属性。

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

from 这样的相对导入。 import b 与 OP 问题中的情况等同于 from pkg import b

但这是怎么发生的呢?要理解这一点,我们应该查看 import.c< /code>python 模块专门针对函数 get_parent()。正如您所看到的,这里列出的函数很长,但一般来说,当它看到相对导入时,它所做的就是尝试用父包替换点 . ,具体取决于 __main__< /code> 模块,同样来自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之前发生了什么?

首先) 来自 .调用 pkg/b.py 中的 import a,按照上面的解释将其翻译为 from pkg import a,这在字节码中又相当于 <代码>导入pkg; getattr(pkg, 'a').但是等一下 a 也是一个模块?!
那么有趣的部分来了,如果我们有类似 from module|package import module 的东西,在这种情况下,将会发生第二次导入,即 import 子句中模块的导入。因此,在 OP 示例中,我们现在需要导入 pkg/a.py,正如您所知,首先我们在 sys.modules 中为我们的新模块设置一个密钥模块将是pkg.a,然后我们继续解释模块pkg/a.py,但在模块pkg/a.py之前code> 完成导入,调用 from 。导入b。

现在到了第二)部分,pkg/b.py将被导入,接下来它将首先尝试导入pkg,因为< code>pkg 已经导入,因此我们的 sys.modules 中有一个键 pkg ,它只会返回该键的值。然后它会import b设置sys.modules中的pkg.b键并开始解释。我们从 到达这一行。导入一个!

但是记住pkg/a.py已经导入,这意味着('pkg.a' in sys.modules) == True所以导入将被跳过,并且仅调用 getattr(pkg, 'a') ,但是会发生什么? python 没有完成导入pkg/a.py!?因此,只会调用 getattr(pkg, 'a') ,这会在 import_from() 函数中引发 AttributeError,这将导致被翻译为ImportError(cannot import name a)

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

编辑:这个答案被重新表述,因为当我尝试再次阅读它时,我评论了我的答案是如何制定的,希望现在它会更有用:)

First let's start with how from import work in python:

Well first let's look at the byte code:

>>> 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        

hmm interesting :), so from foo import bar is translated to first IMPORT_NAME foo which equivalent to import foo and then IMPORT_FROM bar.

Now what IMPORT_FROM do ?

let's see what python do when he found 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;

Well basically he get the the names to import from, which is in our foo() function will be bar, then he pop from the frame stack the value v which is the return of the the last opcode executed which is IMPORT_NAME, then call the function import_from() with this two arguments :

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;
}

As you can see the import_from() function is quiet easy, it try first to get the attribute name from the module v, if it don't exist it raise ImportError else return this attribute.

Now what this have to do with relative import ?

Well relative import like from . import b are equivalent for example in the case that is in the OP question to from pkg import b.

But how this happen ? To understand this we should take a look to the import.c module of python specially to the function get_parent(). As you see the function is quiet long to list here but in general what it does when it see a relative import is to try to replace the dot . with the parent package depending on the __main__ module, which is again from the OP question is the package pkg.

Now let's put all this together and try to figure out why we end up with the behavior in the OP question.

For this it will help us if we can see what python do when doing imports, well it's our lucky day python come already with this feature which can be enabled by running it in extra verbose mode -vv.

So using the command line: 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__._

hmm what just happen before the ImportError ?

First) from . import a in pkg/b.py is called, which is translated as explained above to from pkg import a, which is again in bytecode is equivalent to import pkg; getattr(pkg, 'a'). But wait a minute a is a module too ?!
Well here came the fun part if we have something like from module|package import module in this case a second import will happen which is the import of the module in the import clause. So again in the OP example we need now to import pkg/a.py, and as you know first of all we set in our sys.modules a key for our new module which will be pkg.a and then we continue our interpretation of the module pkg/a.py, but before the module pkg/a.py finish importing it call from . import b.

Now come the Second) part, pkg/b.py will be imported and in it turn it will first attempt to import pkg which because pkg is already imported so there is a key pkg in our sys.modules it will just return the value of that key. Then it will import b set the pkg.b key in sys.modules and start the interpretation. And we arrive to this line from . import a !

But remember pkg/a.py is already imported which mean ('pkg.a' in sys.modules) == True so the import will be skipped, and only the getattr(pkg, 'a') will be called , but what will happen ? python didn't finish importing pkg/a.py !? So only getattr(pkg, 'a') will be called , and this will raise an AttributeError in the import_from() function, which will be translated to ImportError(cannot import name a).

DISCLAIM : This is my own effort to understand what is happening inside the interpreter, i'm far away of being an expert.

EDIt: This answer was rephrased because when i tried to read it again i remarked how my answer was bad formulated, hope now it will be more useful :)

小清晰的声音 2024-12-10 11:22:50

(顺便说一句,相对导入并不重要。使用 from pkg import... 揭示了相同的异常。)

我认为这里发生的事情是 from foo import bar< 之间的区别/code> 和 import foo.bar 是在第一个中,值 bar 可以是 pkg foo 中的模块,也可以是模块 foo 中的变量。在第二种情况下,bar 除了模块/包之外的任何内容都是无效的。

这很重要,因为如果已知 bar 是一个模块,那么 sys.modules 的内容足以填充它。如果它可能是 foo 模块中的变量,那么解释器实际上必须查看 foo 的内容,但是在导入 foo 时,将无效;实际模块尚未填充。

在相对导入的情况下,我们理解 from 。 import bar 表示从包含当前模块的包中导入 bar 模块,但这实际上只是语法糖,. 名称被转换为完全限定名称并传递给 < code>__import__(),因此它看起来就像模棱两可的 from foo import bar

(Incedentally, the relative import doesn't matter. Using from pkg import... reveals the same exception.)

I think what's going on here is that the difference between from foo import bar and import foo.bar is that in the first one, the value bar could be module in pkg foo or it could be a variable in a module foo. In the second case, it's invalid for bar to be anything but a module/package.

This would matter because if bar is known to be a module, then the contents of sys.modules is sufficient to populate it. If it might be a variable in the foo module, then the interpreter must actually look into the contents of foo, but while importing foo, that would be invalid; the actual module has not been populated yet.

In the case of a relative import, we understand from . import bar to mean import the bar module from the package that contains the current module, but this is really just syntactic sugar, the . name is translated to a fully qualified name and passed to __import__(), and thus it looks for all the world like the ambigious from foo import bar

海螺姑娘 2024-12-10 11:22:50

作为附加说明:

我有以下模块结构:

base
 +guiStuff
   -gui
 +databaseStuff
   -db
 -basescript

我希望能够通过导入base.basescript来运行我的脚本,但是由于gui,这失败并出现错误文件有一个 import base.databaseStuff.db 导致 base 的导入。由于 base 仅注册为 __main__ 它导致整个导入的第二次执行和上面的错误,除非我在 base 上面使用外部脚本,因此仅导入 base / basescript 一次。为了防止这种情况,我将以下内容放入我的基本脚本中:

if  __name__ == '__main__' or \
  not '__main__' in sys.modules or \
  sys.modules['__main__'].__file__ != __file__: 
    #imports here

As an additional Note:

I had the following module Structure:

base
 +guiStuff
   -gui
 +databaseStuff
   -db
 -basescript

I wanted to be able to run my script by import base.basescript, however this failed with an error since the gui file had an import base.databaseStuff.db which caused an import of base. Since base was only registered as __main__ it caused a second execution of the whole imports and the error above unless I used an external script above base thus importing base / basescript only once. To prevent this I put the following in my base script:

if  __name__ == '__main__' or \
  not '__main__' in sys.modules or \
  sys.modules['__main__'].__file__ != __file__: 
    #imports here
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文