Ner*_*zle 6 python directory-structure entry-point
我正在开发一个具有以下目录结构的项目:
project/
package1/
module1.py
module2.py
package2/
module1.py
module2.py
main1.py
main2.py
main3.py
...
mainN.py
Run Code Online (Sandbox Code Playgroud)
其中每个mainX.py文件都是一个可执行的 Python 脚本,可从package1、package2或两者导入模块。package1和package2是子包,旨在与项目的其余部分一起分发(而不是独立地)。
标准的做法是将入口点放在顶级目录中。我有N个入口点,所以我把它们都放在顶级目录中。问题是 N 不断增长,所以我的顶级目录被入口点淹没。
我可以将mainX.py文件移动到子目录(例如,project/run),但是所有的package1和package2导入都会中断。我可以将package1和提取package2到一个单独的存储库,并期望它安装在系统上(即,在系统/用户 python 路径中),但这会使安装复杂化。我可以作为前提条件或在运行时修改 Python 路径,但这很混乱,并且可能会带来意想不到的后果。我可以编写一个main.py入口点脚本,其中参数子解析器分别指向run/main1.py, ..., run/mainN.py,但这会引入main.py每个run/mainX.py文件之间的耦合。
这个问题的标准“Pythonic”解决方案是什么?
标准解决方案是为您的入口点使用打包 - 请在此处console_scripts阅读有关入口点规范的信息。此功能可用于在安装时生成脚本包装器,例如...。main1.pymainN.py
由于这些脚本包装器是生成的代码,因此它们根本不存在于项目源目录中,因此混乱的问题(“顶级目录被入口点淹没”)消失了。
\n脚本的实际代码将在包内的某个位置定义,并且main*.py脚本实际挂钩到包内的代码的位置在包元数据中定义。您可以将控制台脚本入口点挂钩到包内的任何可调用项,只要它可以在没有参数的情况下调用(可选参数,即具有默认值的参数就可以了)。
project\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 package1\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 module1.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 module2.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 package2\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 module1.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 module2.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 pyproject.toml\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 scripts\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 __init__.py\nRun Code Online (Sandbox Code Playgroud)\n这是新的目录结构。请注意添加的__init__.py文件,这表明 和package1是package2包而不仅仅是子目录。
对于添加的新文件,这里是scripts/__init__.py:
# these imports should work\n# from package1 import ...\n# from package2.module1 import ...\n\ndef myscript1():\n # put whatever main1.py did here\n print("hello")\n\ndef myscript2():\n # put whatever main2.py did here\n print("world")\nRun Code Online (Sandbox Code Playgroud)\n这些不需要全部位于同一个文件中,您可以将它们实际放置在包中的任何位置,只要您更新[project.scripts]打包定义部分中的挂钩即可。
这是包装定义:
\n[build-system]\nrequires = ["setuptools"]\nbuild-backend = "setuptools.build_meta"\n\n[project]\nname = "mypackage"\nversion = "0.0.1"\n\n[project.scripts]\n"main1.py" = "scripts:myscript1"\n"main2.py" = "scripts:myscript2"\n\n[tool.setuptools]\npackages = ["package1", "package2", "scripts"]\nRun Code Online (Sandbox Code Playgroud)\n现在安装包后,会生成控制台脚本:
\n$ pip install --editable .\n...\nSuccessfully installed mypackage-0.0.1\n$ main1.py\nhello\n$ main2.py\nworld\nRun Code Online (Sandbox Code Playgroud)\n如前所述,这些可执行文件并不位于项目目录中,而是位于站点的脚本目录中,该目录将出现在 $PATH 中。这些脚本是由 生成的pip,使用 distlib 的供应商代码ScriptMaker。如果您查看生成的脚本文件,您会发现它们是简单的包装器,它们只会从包中导入可调用对象,然后调用它。任何参数解析、日志配置等都必须在包代码中处理。
$ ls\nmypackage.egg-info package1 package2 pyproject.toml scripts\n$ which main2.py\n/tmp/project/.venv/bin/main2.py\nRun Code Online (Sandbox Code Playgroud)\n脚本目录的确切位置取决于您的平台,但可以在 Python 中像这样检查:
\n>>> import sysconfig\n>>> sysconfig.get_path("scripts")\n\'/tmp/project/.venv/bin\'\nRun Code Online (Sandbox Code Playgroud)\n