这是一个通用配方,经过修改以适合作为示例,我现在正在使用它来处理作为包编写的 Python 库,其中包含相互依赖的文件,我希望能够在其中零碎地测试它们的一部分。让我们称之为它lib.foo
并说它需要访问lib.fileA
for 函数f1
和f2
, 和lib.fileB
类Class3
。
我已经包括了一些print
电话来帮助说明这是如何工作的。在实践中,您可能希望删除它们(也许还有这from __future__ import print_function
条线)。
这个特殊的例子太简单了,以至于我们真的需要在sys.path
. (在我们确实需要它的情况下,请参阅Lars 的回答,当我们有两个或更多级别的包目录时,然后我们使用- 但在这里也没有真正的伤害。)在没有测试。但是,如果每个导入的文件都插入相同的路径——例如,如果两者都想从包中导入实用程序——这会多次使用相同的路径,所以在样板文件中包含 是很好的。os.path.dirname(os.path.dirname(__file__))
if _i in sys.path
fileA
fileB
sys.path
if _i not in sys.path
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 和 python 3.x 中都具有相同的功能):
如果作为import lib
或from lib import foo
作为从普通代码导入的常规包运行,__package
则 islib
和__name__
is lib.foo
。我们采用第一个代码路径,导入 from.fileA
等。
如果运行为python lib/foo.py
,__package__
将是 None 并且__name__
将是__main__
。
我们采用第二条代码路径。该lib
目录已经存在,sys.path
因此无需添加它。fileA
我们从等进口。
如果在lib
目录中运行 as python foo.py
,则行为与案例 2 相同。
如果在lib
目录中运行 as python -m foo
,行为类似于情况 2 和 3。但是lib
目录的路径不在 中sys.path
,所以我们在导入之前添加它。如果我们运行 Python 然后import foo
.
(由于.
是in sys.path
,我们实际上不需要在此处添加绝对版本的路径。这是我们想要做的更深的包嵌套结构产生影响的地方from ..otherlib.fileC import ...
。如果您不这样做,您可以完全省略所有sys.path
操作。)
笔记
仍然有一个怪癖。如果你从外面运行这整个事情:
$ python2 lib.foo
或者:
$ python3 lib.foo
行为取决于lib/__init__.py
. 如果存在且为空,则一切正常:
Package named 'lib'; __name__ is '__main__'
但是,如果lib/__init__.py
它自己导入routine
以便它可以routine.name
直接导出为lib.name
,你会得到:
$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'
也就是说,模块会被导入两次,一次通过包导入,然后再次导入,__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__'
警告是新的,但警告的行为不是。这是一些人所说的双重进口陷阱的一部分。(有关更多详细信息,请参阅issue 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
当且仅当我们添加了 的一份副本时才删除一份_i
)。