草庐IT

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

coder 2023-05-23 原文

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

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

有关python - Python 中的循环模块依赖和相对导入的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  5. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  7. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  8. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  9. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  10. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

随机推荐