33

我有一个这样的“规范文件结构”(我给出了合理的名称以方便阅读):

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,没有任何变化。

4

4 回答 4

45

PEP 366中给出的“样板”似乎不完整。尽管它设置了__package__变量,但它实际上并不导入包,这也是允许相对导入工作所必需的。 extraneon的解决方案走上了正轨。

请注意,仅包含包含模块的目录是不够的sys.path,需要显式导入相应的包。以下似乎比PEP 366中给出的更好的样板,以确保无论如何调用 python 模块(通过常规import,或 with python -m,或 with 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包含顶层的目录。

于 2011-07-11T19:03:41.370 回答
7

加载代码似乎是这样的:

    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加载包,以便导入可以是相对的。

于 2010-05-31T14:03:16.587 回答
7

受 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 
于 2013-03-20T18:51:10.363 回答
0

这是基于大多数其他答案的最小设置,在 python 2.7 上进行了测试,包布局像这样。它还有一个优点是您可以runme.py从任何地方调用脚本,而且它似乎在做正确的事情——我还没有在更复杂的设置中测试过它,所以请注意购买者......等等。

这基本上是布拉德的回答,上面插入了其他人描述的 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)
于 2015-12-08T11:52:30.373 回答