对于与循环导入问题不同的个人包,Python条件模块对象没有属性错误
问题描述
我在尝试使用我创建的程序包层次结构时遇到了一个‘模块对象没有属性...’错误。该错误让人想起循环导入时出现的错误(即模块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'
这里有三个要点:
模块a、a.b和a.bc如果尚未导入,将按顺序导入
模块在完全完成导入之前,不存在于其父命名空间中。因此,在完全导入
a.b
之前,模块a
没有b
属性。无论模块链有多深,即使
import a.b.c.d.e.f.g
,代码只有一个符号添加到其命名空间:a
。
因此,当您稍后尝试运行a.b.c.d.e.f.g.some_function()
时,解释器必须沿模块链一路向下遍历才能找到该方法。
以下是正在发生的情况
根据您发布的代码,问题似乎存在于alpha/bravo/echo/__init__.py
中的print语句中。当翻译到达那里时,它所做的大致如下:
在sys模块中为Alpha设置空模块对象
运行Alpha/__init__.py中的代码(请注意,此时dir(Alpha)不会包含‘bravo’)
在系统模块中为alpha.bravo设置空模块对象
运行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']
print alpha.bravo.delta.delta_foo(1)
。开始此序列:
- 获取全局变量
alpha
--这将返回仍然为空的alpha
模块。 - 调用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判决,而不是遍历从阿尔法到布拉沃再到德尔塔,它可以从德尔塔模块获取函数并将其赋值给一个局部变量,然后您可以轻松地访问该变量。
相关文章