In Python, "There should be one-- and preferably only one --obvious way to do it." When the Way does not align with yours, welp.
从其他开发框架迁移过来,使用如下的项目目录结构应该还是蛮常见的:
project/
|_ src/ - 源码
|_ test/ - 测试
|_ ... - 项目的其他配置文件
但这样的一个代码组织会带来一些反直觉的问题。当我们建立了两个文件,需要相互引用的时候,这个代码应该怎么写呢?
project/
|_ src/
|_ __init__.py
|_ module.py
|_ main.py
# in src/module.py
def hello():
print('Hello')
直觉上,可以这么写:
# in src/main.py
from module import hello
if __name__ == '__main__':
hello()
然后如果真的运行的话,它也能用:
python src/main.py
Hello
但是如果你恰好装了一个名字叫 module 的包的话,这个玩意就崩掉了:
python src/main.py
Traceback (most recent call last):
File "/.../src/main.py", line 1, in <module>
from module import hello
ImportError: cannot import name 'hello' from 'module' (/.../site-packages/module/__init__.py)
好在 Python 可以指定从相对位置引入:
# in src/main.py
from .module import hello
但真这么写的话,你就会发现直接调脚本调不动了:
python src/main.py
Traceback (most recent call last):
File "/.../src/main.py", line 1, in <module>
from .module import hello
ImportError: attempted relative import with no known parent package
需要按照模块的方式来调用才行:
python -m src.main
Hello
简单研究了一下,为了让这两种调用方式都可行,需要写成这样的一个模式:
# in src/main.py
from src.module import hello
在你完成脚本调试之后,可能会希望把它打包成一个命令行工具。简单用 setuptools 起一个打包过程即可:
# in pyproject.toml
[project]
name = "my-tool"
version = "0.0.1"
dependencies = [
# ...
]
requires-python = ">= 3.11"
[project.scripts]
my-tool = 'src.main:main'
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
不过,真这么打包的话,会发现 setuptools 打包的根是在 src/ 下的。pip install . 之后,它反而又报错了:
my-tool
Traceback (most recent call last):
File "/.../.venv/bin/my-tool", line 3, in <module>
from src.main import main
ModuleNotFoundError: No module named 'src'
如果在打包工具里去掉 src. 的前缀呢?
# in pyproject.toml
# ...
[project.scripts]
my-tool = 'main:main'
这样做虽然 my-tool 脚本可以正常运行了,但是会炸掉剩下东西的引用:
my-tool
Traceback (most recent call last):
File "/.../.venv/bin/my-tool", line 3, in <module>
from main import main
File "/.../site-packages/main.py", line 1, in <module>
from src.module import hello
ModuleNotFoundError: No module named 'src'
要让程序可以正常运行,需要告诉 setuptools 将 src/ 作为打包目标:
# in pyproject.toml
# ...
[tool.setuptools]
packages = ["src"]
这样最终打出来的包才是正常可用的。
不过你可能会立刻意识到:这个包在 site-packages 里的名字是 src. 如果这个包要发布的话,叫这个名字肯定是不行的。所以最好的方法是采用这样的一个结构:
project
|_ my_tool <- 你的包名,取代原有的 src
| |_ __init__.py
| |_ ...
|_ test
然后对应地指定 setuptools 使用同样的包名:
# in pyproject.toml
# ...
[project.scripts]
my-tool = "my_tool.main:main"
[tool.setuptools]
packages = ["my_tool"]
正在加载评论……