当我想要快速、轻松地做许多不同的事情时,我会选择Python - 即我总是会得到许多Python“程序” - 例如一组脚本 - 或者如果我正在玩一些东西,一堆测试程序等 - 即始终是许多不同程序的松散集合。
但是,我会分享某些内容。 例如,如果我正在使用 AI - 我可能有 30 个左右完全不相关的程序 - 但它们都会调用 LLM。
目录结构
所以我在 Git 中总是想要的目录结构就像:
.
└── src/
├── common
├── program1
├── program2
└── program3
不需要:显式设置
但我还没有找到任何好方法使program1、program2、program3引用以任何ide或linter都能理解的方式通用,直接从git中出来。 我知道我可以做一些事情,比如设置 python 路径等 - 但我希望有人能够克隆库 - 更改为 program1 并运行 python program1.py 它就可以工作 - 所以任何需要运行/设置/配置的东西都是只是不是我的选择。
想要:相对导入
我真正想要的是每个程序只做:
from ..common import some_helper
但是当我尝试时,我当然得到
Error: attempted relative import with no known parent package
现在...如果我这样做:
import sys
import os
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parent_dir)
大多数事情实际上都有效,但这就像我必须添加到每个 python 程序中的 4 行晦涩难懂的行 - 并且一些编辑器或 linter 找不到它。
这些程序实际上彼此无关,除了属于同一个存储库的一部分之外,因此任何共享的出身都是非常错误的。 它们通常还有一堆其他文件、数据文件、自述文件等 - 所以将所有内容都放在公共目录之上的同一目录中太混乱了。
我尝试过的替代方案:符号链接
我找到的唯一真正实用的解决方案到目前为止,只是将公共目录符号链接到所有其他目录中 - 它很丑陋 - 但它确实有效。
问题
是否有更好的方法 - 一种更Pythonic的方法?
我想从根本上来说 - 我想要知道如何在没有已知父包的情况下进行相对导入?
所面临的问题是,Python 在没有额外上下文的情况下无法将的项目目录结构识别为包。是对的,直接修改
sys.path
通常不是最干净的解决方案。
以下是一些更 Pythonic 的管理项目中共享代码的方法:
1. 使用
__init__.py
文件创建包:
-
在的
src
目录以及common
、program1
、program2
等目录中创建空的__init__.py
文件。这会将这些目录转变为 Python 包。 -
现在,可以使用相对导入:
python # 在 program1/program1.py 中 from ..common import some_helper
-
优点: 这是 Pythonic 的方法,应该可以与大多数 IDE 和 linter 配合良好。
-
缺点:
它要求在每个子目录中都有
__init__.py
文件,这在某些情况下可能看起来很笨拙。
2. 使用安装包:
-
将的项目构建为一个适当的 Python 包(使用
setup.py
或pyproject.toml
)。 -
在的项目根目录中,运行
pip install -e .
(“可编辑安装”)。这会将的包放在 Python 路径上,同时仍然允许对其进行编辑。 -
现在,可以使用绝对导入:
python # 在 program1/program1.py 中 from my_package.common import some_helper
-
优点: 这是用于共享代码的最强大和最灵活的解决方案,尤其是在计划分发代码时。
- 缺点: 它需要更多的初始设置,并且可能对这个特定用例来说有点过分。
3. 使用命名空间包(对于 Python 3.3+):
-
命名空间包是仅包含其他包的包,它们不需要
__init__.py
文件。 -
可以将
src
变成一个命名空间包,并以这种方式组织的代码。 -
优点: 避免使用
__init__.py
文件,更清晰地分离关注点。 - 缺点: 可能不如其他方法直接,需要了解命名空间包的工作原理。
关于的“没有已知父包”错误:
当运行
program1.py
时,Python 不会自动将
src
的父目录视为包层次结构的一部分。通过将的目录结构变成一个或多个包,将为 Python 提供必要的上下文来理解的相对导入。
符号链接方法:
虽然符号链接可以工作,但它不是一个非常便携或 Pythonic 的解决方案。它可能导致 IDE 和 linter 出现问题,并且在不同的操作系统上可能无法按预期工作。
建议:
对于的用例,我建议使用第一个或第二个选项。如果只需要一个简单的解决方案来使的代码正常工作,那么使用
__init__.py
文件创建包是最直接的方法。如果计划更广泛地分发的代码或需要更强大的解决方案,那么将其构建为一个适当的包是一个更好的选择。