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

结构如下:

├── 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

其他回答

对于2021年的读者:如果你对pip没有信心,请安装-e:

考虑一下这个层次结构,正如Python 3中相对导入的答案所推荐的那样:

MyProject
├── src
│   ├── bot
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── sib1.py
│   └── mod
│       ├── __init__.py
│       └── module1.py
└── main.py

main.py的内容,这是起点,我们在这里使用绝对导入(没有前导点):

from src.bot import main


if __name__ == '__main__':
    main.magic_tricks()

bot/main.py的内容,它利用了显式的相对导入:

from .sib1 import my_drink                # Both are explicit-relative-imports.
from ..mod.module1 import relative_magic

def magic_tricks():
    # Using sub-magic
    relative_magic(in=["newbie", "pain"], advice="cheer_up")
    
    my_drink()
    # Do your work
    ...

原因如下:

当执行python MyProject/main.py时,/到/MyProject的路径被添加到sys.path中。 绝对导入import src。机器人会读的。 从…mod部分意味着它将上升一级到MyProject/src。 我们能看看吗?是的,因为路径/to/MyProject被添加到sys.path中。

所以重点是:

我们应该把主脚本放在MyProject/src旁边,因为当做相对引用时,我们不会离开src,绝对导入导入src。为我们提供合适的作用域:src/ scope。

参见:ModuleNotFoundError:没有名为“sib1”的模块

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

文档

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(在对导入进行适当更改之后),则会抛出异常。即使它在其父目录中被脚本调用时可以工作,但如果它认为自己位于包的顶层,它就不能工作。

我还没有对python学有必要的理解,以了解在不相关的项目之间共享代码的预期方式,而没有兄弟姐妹/亲戚导入黑客。在那一天到来之前,这就是我的解决方案。用于从..导入内容的示例或测试。\api,它看起来像:

import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key

如果您正在使用pytest,那么pytest文档描述了如何从单独的测试包引用源包的方法。

建议的项目目录结构为:

setup.py
src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

setup.py文件的内容:

from setuptools import setup, find_packages

setup(name="PACKAGENAME", packages=find_packages())

以可编辑模式安装软件包:

pip install -e .

这篇文章引用了Ionel Cristian matrie什的博客文章。