如何使同一命名空间下的第二个 __init__.py 文件执行

3

这是我的目录结构

.
|-- path1
|   `-- mynms
|       |-- __init__.py
|       `-- app1
|           |-- __init__.py
|           `-- foo.py
|-- path2
|   `-- mynms
|       |-- __init__.py
|       `-- app2
|           |-- __init__.py
|           `-- bar.py
`-- user.py

文件内容:

$ cat user.py
#!/usr/bin/python

import sys
sys.path.append('path1')
sys.path.append('path2')

from mynms.app2.foo import foo
from mynms.app2.bar import bar

foo()
bar()

$ cat path1/mynms/__init__.py;echo ==============;cat path2/mynms/__init__.py
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

print "I am path1/mynms/__init__.py"
==============
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

print "I am path2/mynms/__init__.py"

$ cat path1/mynms/app1/foo.py; echo ============; cat path2/mynms/app2/bar.py
def foo():
  print "foo!"
============
def bar():
  print "bar!"

问题:当我运行user.py时,我只得到了path1/__init__.py的输出,而没有得到path2的输出。有什么办法可以解决这个问题吗?

$ ./user.py
I am path1/mynms/__init__.py        -----> Why is 'I am path2/mynms/__init__.py not printed?
foo!
bar!

你有两个同名的包,这通常是不被赞同的。你的测试用例包必须使用相同的名称吗?你能否将第二个重命名? - tdelaney
名称testcase具有误导性,让我来更改它。 - balki
我不能将它们更改为不同的命名空间,因为有其他原因。它们是同一开发团队的两个应用程序,但出于部署目的必须保持在不同的层次结构中。 - balki
Python 2默认不支持命名空间包。setuptools提供了命名空间支持,但要复制其行为需要进行大量的修改。当我有更多时间时,我可以总结一下setuptools的功能,但你也可以直接使用它来打包你的项目,并让setuptools为你管理命名空间。 - Martijn Pieters
3个回答

3
当你写下import mynms.app2.foo或者from mynms.app2.foo import foo时,Python会执行以下步骤:
  • 通过查找文件mynms/__init__.pypath1中导入mynms
  • 通过查找文件mynms/app1/__init__.py(或mynms/app1.py)导入mynms.app1
  • 通过查找文件mynms/app1/foo.py(或mynms/app1/foo/__init__.py)导入mynms.app1.foo
在下一个导入语句import mynms.app2.bar中,Python执行以下操作:
  • 导入mynms - 它已被导入,因此无需执行任何操作。 (您可以检查sys.modules['mynms']以查看它是否已经导入。如果没有,则会引发KeyError。)
  • 再次导入mynms.app2 - 它也已经被导入。
  • 通过读取文件mynms/app2/bar.pypath2中导入mynms.app2.bar
Python没有(合理的)方式从两个不同的文件中两次导入mynms模块。
如果您有初始化代码,则需要将它们放在模块mynms.app1mynms.app2中,以便它们具有不同的名称,即文件mynms/app1/__init__.pymynms/app2/__init__.py

1
如Martijn Pieters在评论中已经提到的,Python 2不支持以一堆包的形式分发的包。但是如果您使用setuptools(很可能会这样做),那么您可以模拟这样的包。
唯一的问题是这些包应该有正确的元信息,以便setuptools可以将几个子包合并成一个虚拟的单一包。通常,信息被传递给setup.py中的setup()函数,并在安装后存储在您的site-packages目录中。这意味着分发包的人必须采取一些措施来正确地准备它们,而您不能仅仅将它们放在PATH中就能让它们工作。
此处链接是相应文档的链接。请准备好花费一些时间,因为PEAK文档并不总是足够快速入门。

0

我同意其他评论者的观点 - 这是糟糕的代码结构,应该尽快进行重构。然而,如果你真的、真的、真的不能或者还不能做到,那么你可以使用标准库中的 imp 模块来搞乱导入内部:

https://docs.python.org/2/library/imp.html

例如,您可以通过执行以下操作导入不在正常Python模块搜索路径中的文件:

/Users/me/test.py:

def run_foo():
  print "hi got here" 

还有另一个脚本:

import imp
foo = imp.load_source("foo", "/Users/me/test.py")
foo.run_foo()

再次强调,这种方式是在与Python对抗而不是与其合作。但这是一种值得掌握的好技巧。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接