这里有一个通用的方法,经过修改以适合作为示例,我现在正在使用它来处理作为包编写的Python库,这些库包含相互依赖的文件,我希望能够在其中逐个测试其中的部分。让我们调用这个lib.foo,假设它需要访问函数f1和f2的lib.fileA,以及Class3类的lib.file B。
我加入了一些打印调用,以帮助说明这是如何工作的。实际上,您可能希望删除它们(也可能删除from __future_import print_function行)。
这个特定的例子太简单了,无法显示何时我们真的需要在sys.path中插入一个条目。(当我们有两个或更多级别的包目录,然后使用os.path.dirname(os.path.deirname(__file__))时,请参阅Lars的回答,了解我们确实需要它的情况,但在这里也不会有什么问题。)在sys.path测试中不使用if_i也足够安全。但是,如果每个导入的文件都插入了相同的路径,例如,如果fileA和fileB都想从包中导入实用程序,这会多次将sys.path与相同的路径混淆,因此最好在样板中的sys.path中不包含if_i。
from __future__ import print_function # only when showing how this works
if __package__:
print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
from .fileA import f1, f2
from .fileB import Class3
else:
print('Not a package; __name__ is {!r}'.format(__name__))
# these next steps should be used only with care and if needed
# (remove the sys.path manipulation for simple cases!)
import os, sys
_i = os.path.dirname(os.path.abspath(__file__))
if _i not in sys.path:
print('inserting {!r} into sys.path'.format(_i))
sys.path.insert(0, _i)
else:
print('{!r} is already in sys.path'.format(_i))
del _i # clean up global name space
from fileA import f1, f2
from fileB import Class3
... all the code as usual ...
if __name__ == '__main__':
import doctest, sys
ret = doctest.testmod()
sys.exit(0 if ret.failed == 0 else 1)
这里的想法是这样的(请注意,在python2.7和python3.x中,这些功能都是相同的):
如果作为import lib或from lib运行import foo作为从普通代码导入的常规包,__package是lib,__name__是lib.foo。我们采用第一个代码路径,从.fileA导入,等等。如果以python-lib/foo.py运行,__package__将为None,__name__将为__main__。我们采用第二个代码路径。lib目录已经在sys.path中,因此不需要添加它。我们从fileA等导入。如果在lib目录中作为python-foo.py运行,则行为与情况2相同。如果在lib目录中以python-mfoo的形式运行,则行为类似于情况2和3。但是,指向lib目录的路径不在sys.path中,因此我们在导入之前添加它。如果我们运行Python,然后导入foo,这同样适用。(由于.在sys.path中,所以我们实际上不需要在这里添加路径的绝对版本。这就是我们希望从.otherlib.fileC import…中执行的更深层次的包嵌套结构的地方。如果不这样做,可以完全省略所有sys.path操作。)
笔记
还有一个怪癖。如果你在外面做这件事:
$ python2 lib.foo
or:
$ python3 lib.foo
行为取决于lib/__init__.py的内容。如果存在并且为空,则一切正常:
Package named 'lib'; __name__ is '__main__'
但如果lib/__init__.py本身导入例程,以便它可以将routine.name直接导出为lib.name,则会得到:
$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'
也就是说,模块被导入两次,一次通过包导入,另一次作为__main__导入,以便运行主代码。Python 3.6及更高版本对此发出警告:
$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'
警告是新的,但关于行为的警告不是。这是一些人所说的双重进口陷阱的一部分。(更多详情请参见第27487期。)Nick Coghlan表示:
下一个陷阱存在于所有当前版本的Python(包括3.3)中,可以总结为以下一般准则:“永远不要将包目录或包中的任何目录直接添加到Python路径”。
请注意,虽然我们在这里违反了该规则,但我们只在加载的文件未作为包的一部分加载时才这样做,并且我们的修改专门设计为允许我们访问该包中的其他文件。(而且,正如我所指出的,我们可能根本不应该对单级包这样做。)如果我们想更加干净,我们可以将其重写为,例如:
import os, sys
_i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _i not in sys.path:
sys.path.insert(0, _i)
else:
_i = None
from sub.fileA import f1, f2
from sub.fileB import Class3
if _i:
sys.path.remove(_i)
del _i
也就是说,我们修改sys.path足够长的时间以实现导入,然后将其恢复原样(如果并且仅当我们添加了一个_i副本时,删除一个_ii副本)。