我有一个像这样的“规范文件结构”(我给出了明智的名称来简化阅读):
mainpack/
__main__.py
__init__.py
- helpers/
__init__.py
path.py
- network/
__init__.py
clientlib.py
server.py
- gui/
__init__.py
mainwindow.py
controllers.py
在此结构中,例如,每个包中包含的模块可能希望通过相对导入来访问helpers
实用程序,例如:
# network/clientlib.py
from ..helpers.path import create_dir
程序以这种方式使用__main__.py
文件“作为脚本”运行:
python mainpack/
尝试关注PEP 366我已将__main__.py
这些行放入其中:
___package___ = "mainpack"
from .network.clientlib import helloclient
但是在跑步的时候:
$ python mainpack
Traceback (most recent call last):
File "/usr/lib/python2.6/runpy.py", line 122, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "/usr/lib/python2.6/runpy.py", line 34, in _run_code
exec code in run_globals
File "path/mainpack/__main__.py", line 2, in <module>
from .network.clientlib import helloclient
SystemError: Parent module 'mainpack' not loaded, cannot perform relative import
怎么了?处理和有效使用相对进口的正确方法是什么?
我也尝试将当前目录添加到PYTHONPATH,没有任何变化。
答案 0 :(得分:41)
PEP 366中给出的“样板”似乎不完整。虽然它设置了__package__
变量,但它实际上并不导入包,这也是允许相对导入工作所必需的。 extraneon 的解决方案正在走上正轨。
请注意,仅仅在sys.path
中包含包含模块的目录是不够的,需要显式导入相应的包。以下似乎是比PEP 366中给出的更好的样板,以确保无论如何调用python模块(通过常规import
或python -m
,都可以执行,或者python
,来自任何地方):
# boilerplate to allow running as script directly
if __name__ == "__main__" and __package__ is None:
import sys, os
# The following assumes the script is in the top level of the package
# directory. We use dirname() to help get the parent directory to add to
# sys.path, so that we can import the current package. This is necessary
# since when invoked directly, the 'current' package is not automatically
# imported.
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(1, parent_dir)
import mypackage
__package__ = str("mypackage")
del sys, os
# now you can use relative imports here that will work regardless of how this
# python file was accessed (either through 'import', through 'python -m', or
# directly.
如果脚本不在包目录的顶层,并且您需要在顶层下面导入模块,则必须重复os.path.dirname
,直到parent_dir
为包含目录顶级。
答案 1 :(得分:7)
加载代码似乎类似于this:
try:
return sys.modules[pkgname]
except KeyError:
if level < 1:
warn("Parent module '%s' not found while handling "
"absolute import" % pkgname, RuntimeWarning, 1)
return None
else:
raise SystemError, ("Parent module '%s' not loaded, cannot "
"perform relative import" % pkgname)
这让我觉得你的模块可能不在sys.path上。如果您启动Python(通常)并在提示符下键入“import mainpack”,它会做什么? 应能够找到它。
我自己尝试过并得到了同样的错误。读了一下后,我找到了以下解决方案:
# foo/__main__.py
import sys
mod = __import__('foo')
sys.modules["foo"]=mod
__package__='foo'
from .bar import hello
hello()
对我来说似乎有点骇人听闻,但确实有效。诀窍似乎是确保加载包foo
,因此导入可以是相对的。
答案 2 :(得分:6)
受extraneon和taherh的答案的启发,这里有一些代码运行文件树,直到用完__init__.py
个文件来构建完整的软件包名称。这绝对是hacky,但无论目录树中文件的深度如何,它似乎都能正常工作。似乎绝对导入严重鼓励。
import os, sys
if __name__ == "__main__" and __package__ is None:
d,f = os.path.split(os.path.abspath(__file__))
f = os.path.splitext(f)[0]
__package__ = [f] #__package__ will be a reversed list of package name parts
while os.path.exists(os.path.join(d,'__init__.py')): #go up until we run out of __init__.py files
d,name = os.path.split(d) #pull of a lowest level directory name
__package__.append(name) #add it to the package parts list
__package__ = ".".join(reversed(__package__)) #create the full package name
mod = __import__(__package__) #this assumes the top level package is in your $PYTHONPATH
sys.modules[__package__] = mod #add to modules
答案 3 :(得分:0)
这是基于大多数其他答案的最小设置,在python 2.7上测试,包装布局如此。它还有一个优点,你可以从任何地方调用runme.py
脚本,而似乎就像它做了正确的事情 - 我还没有测试过它更复杂的设置,所以需要注意......等等。
这基本上是Brad在上面回答的插入到其他人描述的sys.path中。
packagetest/
__init__.py # Empty
mylib/
__init__.py # Empty
utils.py # def times2(x): return x*2
scripts/
__init__.py # Empty
runme.py # See below (executable)
runme.py
看起来像这样:
#!/usr/bin/env python
if __name__ == '__main__' and __package__ is None:
from os import sys, path
d = path.dirname(path.abspath(__file__))
__package__ = []
while path.exists(path.join(d, '__init__.py')):
d, name = path.split(d)
__package__.append(name)
__package__ = ".".join(reversed(__package__))
sys.path.insert(1, d)
mod = __import__(__package__)
sys.modules[__package__] = mod
from ..mylib.utils import times2
print times2(4)