我试着通读关于兄弟姐妹导入的问题,甚至 软件包文档,但我还没找到答案。

结构如下:

├── LICENSE.md
├── README.md
├── api
│   ├── __init__.py
│   ├── api.py
│   └── api_key.py
├── examples
│   ├── __init__.py
│   ├── example_one.py
│   └── example_two.py
└── tests
│   ├── __init__.py
│   └── test_one.py

示例和测试目录中的脚本如何从 API模块和从命令行运行?

另外,我希望避免对每个文件都使用难看的sys.path.insert。肯定 这可以在Python中完成,对吧?


当前回答

对于兄弟包导入,您可以使用[sys. xml]的insert或append方法。路径][2]模块:

if __name__ == '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

如果你启动你的脚本,这将工作如下:

python examples/example_one.py
python tests/test_one.py

另一方面,你也可以使用相对导入:

if __name__ == '__main__' and if __package__ is not None:
    import ..api.api

在这种情况下,你必须使用'-m'参数启动你的脚本(注意,在这种情况下,你不能给出'.py'扩展名):

python -m packageName.examples.example_one
python -m packageName.tests.test_one

当然,你可以混合使用这两种方法,这样你的脚本无论如何调用都能正常工作:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api

其他回答

七年之后

由于我把答案写在下面,修改sys.;Path仍然是一种快速而肮脏的技巧,适用于私有脚本,但是已经有了一些改进

安装包(在virtualenv中或不在virtualenv中)会给你想要的,尽管我建议使用pip来完成,而不是直接使用setuptools(并使用setup.cfg存储元数据) 使用-m标志并作为包运行也可以(但是如果您想将工作目录转换为可安装包,则会有点尴尬)。 具体来说,对于测试,pytest能够在这种情况下找到api包,并负责sys. exe。给你的路径技巧

所以这取决于你想做什么。但是,在您的情况下,由于您的目标似乎是在某种程度上制作一个适当的包,因此通过pip -e安装可能是您的最佳选择,即使它还不完美。

旧的答案

正如在其他地方已经说过的,可怕的事实是,你必须做一些丑陋的hack来允许从__main__模块的兄弟模块或父包导入。该问题在PEP 366中有详细说明。PEP 3122试图以更合理的方式处理导入,但Guido拒绝了它

唯一的用例似乎是运行发生的脚本 在一个模块的目录中,我总是把它看作一个 反模式。

(在这里)

不过,我经常使用这种模式

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

这里的路径[0]是运行脚本的父文件夹,目录(路径[0])是顶级文件夹。

虽然我还不能使用相对导入,但它确实允许从顶层(在示例api的父文件夹中)进行绝对导入。

在主文件中添加以下内容:

import sys
import os 
sys.path.append(os.path.abspath(os.path.join(__file__,mainScriptDepth)))

mainScriptDepth =主文件从项目根目录的深度。

这是你的案例mainScriptDepth = "../../"。然后,您可以通过指定路径(从api。API import *)从你的项目的根。

我想评论一下np8提供的解决方案,但我没有足够的声誉,所以我只想说,你可以完全按照他们建议的那样创建一个setup.py文件,然后执行pipenv install——dev -e。将其转换为可编辑的依赖项。然后你的绝对导入将工作,例如从api。API import foo,你就不必在系统范围的安装上瞎折腾了。

文档

您需要查看import语句是如何在相关代码中编写的。如果examples/example_one.py使用以下import语句:

import api.api

...然后,它期望项目的根目录位于系统路径中。

不需要任何hack(如你所说)就能支持它的最简单的方法就是从顶级目录运行这些例子,就像这样:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

TLDR

这种方法不需要setuptools、路径hack、额外的命令行参数,也不需要在项目的每个文件中指定包的顶层。

只要在你调用的__main__的父目录中创建一个脚本,并从那里运行所有内容。进一步的解释请继续阅读。

解释

这可以在不使用新路径、额外的命令行参数或向每个程序添加代码来识别其兄弟程序的情况下完成。

这个失败的原因,我相信之前已经提到过,是被调用的程序将它们的__name__设置为__main__。当这种情况发生时,被调用的脚本接受自己位于包的顶层,并拒绝识别同级目录中的脚本。

但是,目录顶层下的所有内容仍然可以识别顶层下的任何其他内容。这意味着要让同级目录中的文件相互识别/利用,惟一需要做的就是从父目录中的脚本调用它们。

概念证明 在具有以下结构的目录中:

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py包含以下代码:

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1 / call.py包含:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

而sib2/ callsibb .py包含:

def CallSib():
    print("Got Called")

if __name__ == '__main__':
    CallSib()

如果你重现这个例子,你会注意到调用Main.py会导致“Got Called”被打印出来,就像在sib2/ callsibb .py中定义的那样,即使sib2/ callsibb .py是通过sib1/call.py调用的。但是,如果直接调用sib1/call.py(在对导入进行适当更改之后),则会抛出异常。即使它在其父目录中被脚本调用时可以工作,但如果它认为自己位于包的顶层,它就不能工作。