Python条件下“模块对象没有属性”错误与个人包区别于循环导入问题

14 投票
2 回答
13174 浏览
提问于 2025-04-17 10:59

我在使用自己创建的包结构时,遇到了“模块对象没有这个属性...”的错误。这个错误让我想起了循环导入的问题(比如模块a导入模块b,而模块b又导入模块a),但我在这里看不到这个问题。我查阅了很多类似错误的帖子,但没有一个解释能完全符合我的情况。

这个问题出现在python 2.7.1和python 2.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(想着可以在这里暴露相关的类和方法,然后脚本可以执行'import alpha.bravo'),问题就出现了。

代码:

alpha/__init__.py

(blank)

alpha/bravo/__init__.py

import alpha.bravo.charlie

alpha/bravo/charlie.py

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

alpha/bravo/delta.py

def delta_foo(x): return str(x)

alpha/bravo/echo.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'

但是,如果我在alpha/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

我已经找到了解决这个问题的方法,但我还是想理解一下它的原因。谢谢。

2 个回答

1

与其使用绝对导入,不如试试相对导入。

比如说:

alpha/bravo/_init_.py

import alpha.bravo.charlie

应该是

import charlie

否则,很可能会出现循环导入的问题。也就是说,如果你在charlie里面导入alpha.bravo.charlie,这就意味着

alpha/__init__.py
bravo/__init__.py
charlie/__init__.py 

所有的模块都会被加载(或者说,因为它们已经加载过,所以被阻止再次加载)。这可能就是你遇到的问题所在。

15

这是为什么

(我认为,这主要是通过http://docs.python.org/faq/programming.html#how-can-i-have-modules-that-mutually-import-each-other的解释来支持的)

当Python解释器遇到像import a.b.c这样的语句时,它会按照以下步骤进行处理。用伪Python表示:

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.b.c还没有被导入,它们会按顺序被导入。

  2. 一个模块在其父模块的命名空间中不存在,直到它完全被导入。因此,模块aa.b完全导入之前,不会有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中的打印语句。当解释器到达那里时,大致做了以下事情:

  1. 在sys.modules中为alpha设置一个空的模块对象。

  2. 运行alpha/__init__.py中的代码(注意此时dir(alpha)不会包含'bravo')。

  3. 在sys.modules中为alpha.bravo设置一个空的模块对象。

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

4.1 在sys.modules中为alpha.bravo.charlie设置一个空的模块对象。

4.2 运行alpha/bravo/charlie/__init__.py中的代码:

4.2.1 在sys.modules中为alpha/bravo/echo设置一个空的模块对象。

4.2.2 运行alpha/bravo/echo/__init__.py中的代码:

4.2.2.1 在sys.modules中为alpha/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.modules中的所有模块调用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(alpha, 'bravo') -- 这失败了,因为alpha.bravo还没有完成初始化,所以bravo还没有被插入到alpha的符号表中。

这和循环导入时发生的情况是一样的 -- 模块还没有完成初始化,所以符号表没有完全更新,属性访问失败。

如果你把echo/__init__.py中的问题行替换成这个:

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

那可能会有效,因为delta已经完全初始化。但是在bravo完成之前(在echo和charlie返回之后),alpha的符号表不会更新,你将无法通过它访问bravo。

此外,正如@Rik Poggi所说,如果你把导入行改成

from alpha.bravo.delta import delta_foo

那么这将有效。在这种情况下,因为from alpha.bravo.delta直接访问sys.modules字典,而不是从alpha到bravo再到delta进行遍历,它可以从delta模块获取函数并将其分配给一个局部变量,这样你就可以毫无问题地访问它。

撰写回答