对于与循环导入问题不同的个人包,Python条件模块对象没有属性错误

2022-03-31 00:00:00 python python-import

问题描述

我在尝试使用我创建的程序包层次结构时遇到了一个‘模块对象没有属性...’错误。该错误让人想起循环导入时出现的错误(即模块a导入b,模块b导入a),但我在这里看不到这个问题。我看过很多帖子都有类似的错误,但我认为所有的解释都不太符合。

在python2.7.1和python2.4.3中可以看到这一点。

我已将其简化为以下示例:

考虑以下层级结构(请参阅下面的代码):

alpha
alpha/__init__.py
alpha/bravo
alpha/bravo/__init__.py
alpha/bravo/charlie.py
alpha/bravo/delta.py
alpha/bravo/echo.py

Charlie导入的模块ECHO,而Echo又导入Delta。如果Alpha/bravo/__init__.py(如Alpha/__init__.py)基本上为空,则脚本可以执行以下操作:

import alpha.bravo.charlie

如果我尝试在Alpha/bravo/__init__.py中导入alpha.bravo.charlie(考虑到我可以在那里呈现相关的类/方法,而脚本将执行‘导入alpha.bravo’),问题就会浮出水面。

编码:

Alpha/__init__.py

(blank)

pha/bravo/__init__.py

import alpha.bravo.charlie

pha/bravo/charlie.py

import alpha.bravo.echo
def charlie_foo(x): return str(x)
def charlie_bar(x): return alpha.bravo.echo.echo_biz()

pha/bravo/delta.py

def delta_foo(x): return str(x)

Alpha/bravo/ech.py

import alpha.bravo.delta
print alpha.bravo.delta.delta_foo(1)
def echo_biz(): return 'blah'

如果我尝试:

python -c 'import alpha.bravo'

我得到:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/kmkc980/svn/working/azcif/python/lib/alpha/bravo/__init__.py", line 1, in <module>
    import alpha.bravo.charlie
  File "/home/kmkc980/svn/working/azcif/python/lib/alpha/bravo/charlie.py", line 1, in <module>
    import alpha.bravo.echo
  File "/home/kmkc980/svn/working/azcif/python/lib/alpha/bravo/echo.py", line 2, in <module>
    print alpha.bravo.delta.delta_foo(1)
AttributeError: 'module' object has no attribute 'bravo'

但是,如果我注释掉pha/bravo/__init__.py中的导入行,那么一切似乎都很正常:

python -c 'import alpha.bravo'

python -c 'import alpha.bravo.charlie'
1

此外,如果我使用上面相同的代码(包括Alpha/bravo/__init__.py中的导入行),但编辑所有内容以排除层次结构的‘Alpha’级别,它似乎工作得很好。

因此,该层次结构现在仅为:

bravo
bravo/__init__.py
bravo/charlie.py
bravo/delta.py
bravo/echo.py

我将所有行的"alpha.bravo.*"更改为"bravo*"

那么没问题:

python -c 'import bravo'
1

我已经能够解决这个问题,但我仍然希望了解它。谢谢。


解决方案

原因如下

(我相信,这主要得到http://docs.python.org/faq/programming.html#how-can-i-have-modules-that-mutually-import-each-other的解释的支持)

当Python解释器遇到import a.b.c形式的行时,它将执行以下步骤。在伪蟒蛇中:

for module in ['a', 'a.b', 'a.b.c']:
    if module not in sys.modules:
        sys.modules[module] = (A new empty module object)
        run every line of code in module # this may recursively call import
        add the module to its parent's namespace
return module 'a'

这里有三个要点:

  1. 模块a、a.b和a.bc如果尚未导入,将按顺序导入

  2. 模块在完全完成导入之前,不存在于其父命名空间中。因此,在完全导入a.b之前,模块a没有b属性。

  3. 无论模块链有多深,即使import a.b.c.d.e.f.g,代码只有一个符号添加到其命名空间:a

因此,当您稍后尝试运行a.b.c.d.e.f.g.some_function()时,解释器必须沿模块链一路向下遍历才能找到该方法。

以下是正在发生的情况

根据您发布的代码,问题似乎存在于alpha/bravo/echo/__init__.py中的print语句中。当翻译到达那里时,它所做的大致如下:

  1. 在sys模块中为Alpha设置空模块对象

  2. 运行Alpha/__init__.py中的代码(请注意,此时dir(Alpha)不会包含‘bravo’)

  3. 在系统模块中为alpha.bravo设置空模块对象

  4. 运行Alpha/bravo/__init__.py中的代码:

4.1在系统模块中为alpha.bravo.charlie设置空模块对象

4.2运行Alpha/bravo/charlie/__init__.py:

中的代码

4.2.1在系统模块中为Alpha/Bravo/ECHO设置空模块对象

4.2.2运行Alpha/bravo/ECHO/__init__.py:

中的代码

4.2.2.1在系统模块中为α/bravo/Delta设置空模块对象

4.2.2.2运行Alpha/bravo/Delta/__init__.py中的代码--该操作结束,因此将‘Delta’添加到‘alpha.bravo’的符号中。

4.2.2.3将‘Alpha’添加到Echo的符号中。这是import alpha.bravo.delta中的最后一步。

此时,如果我们对sys.Module中的所有模块调用dir(),我们将看到以下内容:

  • ‘Alpha’:['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__return__'](基本上为空)

  • ‘alpha.bravo’:['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'delta'](Delta已导入完毕,所以在这里)

  • ‘alpha.bravo.charlie’:['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__'](空)

  • ‘alpha.bravo.Delta’:['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__return__', 'delta.foo'](这是唯一已完成的)

  • ‘alpha.bravo.echo’:['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__return__', 'alpha']

现在,解释器继续使用Alpha/bravo/ECHO/__init__.py,其中遇到行print alpha.bravo.delta.delta_foo(1)。开始此序列:

  1. 获取全局变量alpha--这将返回仍然为空的alpha模块。
  2. 调用getattr(pha,‘bravo’)--失败,因为alpha.bravo还没有完成初始化,所以bravo还没有插入到Alpha的符号表中。

这与循环导入期间发生的情况相同--模块未完成初始化,因此符号表未完全更新,属性访问失败。

如果您要用以下内容替换ECHO/__init__.py中有问题的行:

import sys
sys.modules['alpha.bravo.delta'].delta_foo(1)

这可能会起作用,因为增量已完全初始化。但在BRAVO完成之前(在Echo和Charlie返回之后),Alpha的符号表不会被更新,并且您将无法通过它访问BRAVO。

另外,如@Rik Poggi所述,如果将导入行更改为

from alpha.bravo.delta import delta_foo

那么这将会奏效。在本例中,因为from alpha.bravo.delta直接转到sys.MODULES判决,而不是遍历从阿尔法到布拉沃再到德尔塔,它可以从德尔塔模块获取函数并将其赋值给一个局部变量,然后您可以轻松地访问该变量。

相关文章