这是一个通用的配方,经过修改以适合作为示例,我现在使用它来处理以程序包形式编写的Python库,其中包含相互依赖的文件,我希望能够逐个测试其中的某些部分。让我们称之为lib.foo
它,它需要lib.fileA
对函数f1
和f2
和lib.fileB
类进行访问Class3
。
我打了几个print
电话,以帮助说明这是如何工作的。实际上,您可能希望将其删除(也许还删除该from __future__ import print_function
行)。
这个特定的例子太简单了,无法显示何时确实需要在中插入条目sys.path
。(见拉尔斯的回答为我们的情况下,就需要它,当我们有包目录中的两个或两个以上的水平,然后我们使用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
则is lib
和__name__
is lib.foo
。我们采用第一个代码路径,从.fileA
等导入。
如果运行为python lib/foo.py
,__package__
则将为None且__name__
将为__main__
。
我们采用第二条代码路径。该lib
目录已经存在,sys.path
因此无需添加它。我们从fileA
等导入
如果在lib
目录中以身份运行python foo.py
,则其行为与情况2相同。
如果在lib
as目录中运行python -m foo
,其行为类似于情况2和3。但是,lib
目录的路径不在in中sys.path
,因此我们在导入之前将其添加。如果我们先运行Python然后运行,则同样适用import foo
。
(由于.
是在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__'
该警告是新的,但警告说,有关的行为是不能。这就是所谓的双重导入陷阱的一部分。(有关其他详细信息,请参见问题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
)。