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

结构如下:

├── 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中完成,对吧?


当前回答

如果您正在使用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什的博客文章。

其他回答

对于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”的模块

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

import api.api

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

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

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

你不需要也不应该侵入系统。路径,除非它是必要的,在这种情况下,它不是。使用:

import api.api_key # in tests, examples

从项目目录运行:python -m tests.test_one。

你可能应该把测试(如果它们是api的单元测试)移动到api内部并运行python -m api。测试运行所有测试(假设有__main__.py)或python -m api.test。Test_one来运行Test_one。

你也可以从示例中删除__init__.py(它不是Python包),并在安装api的virtualenv中运行示例,例如pip install -e。在virtualenv中,如果你有正确的setup.py,就会安装API包。

我还没有对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

存在的问题:

你只是不能让import mypackage在test.py中工作。你将需要一个可编辑的安装,改变到路径,或改变__name__和路径

demo
├── dev
│   └── test.py
└── src
    └── mypackage
        ├── __init__.py
        └── module_of_mypackage.py

--------------------------------------------------------------
ValueError: attempted relative import beyond top-level package

解决方案:

import sys; sys.path += [sys.path[0][:-3]+"src"]

在尝试在test.py中导入之前,请将上述内容放在上面。这是它。你现在可以导入mypackage了。

这在Windows和Linux上都可以工作。它也不会关心从哪个路径运行脚本。它足够短,可以拍打任何你需要它的地方。

为什么有效:

The sys.path contains the places, in order, where to look for packages when attempting imports if they are not found in installed site packages. When you run test.py the first item in sys.path will be something like /mnt/c/Users/username/Desktop/demo/dev i.e.: where you ran your file. The oneliner will simply add the sibling folder to path and everything works. You will not have to worry about Windows vs Linux file paths since we are only editing the last folder name and nothing else. If you project structure is already set in stone for your repository we can also reasonably just use the magic number 3 to slice away dev and substitute src