在标准项目目录结构中调用脚本(Python bin 子目录的路径)
Calling script in standard project directory structure (Python path for bin subdirectory)
我正在尝试将我的 Python 代码放入用于使用 setup.py
和 PyPI 进行部署的标准目录结构中。对于一个名为 mylib 的 Python 库,它将是这样的:
mylibsrc/
README.rst
setup.py
bin/
some_script.py
mylib/
__init.py__
foo.py
通常还有一个 test/
子目录,但我还没有尝试编写单元测试。在 official Python packaging documentation 中可以找到将脚本放在 bin/
子目录中的建议。
当然,脚本以如下代码开头:
#!/usr/bin/env python
from mylib.foo import something
something("bar")
这在最终部署脚本(例如到 devpi)然后使用 pip 安装它时效果很好。但是如果我直接从源目录 运行 脚本,就像我在对 library/script 开发新的更改时所做的那样,我会得到这个错误:
ImportError: No module named 'mylib'
即使当前工作目录是根目录 mylibsrc/
并且我通过键入 ./bin/some_script.py
运行 脚本也是如此。这是因为 Python 开始在脚本目录 运行 中搜索包(即来自 bin/
),而不是当前工作目录。
什么是在开发包时使 运行 脚本变得容易的永久性好方法?
这是一个relevant other question(特别是对第一个答案的评论)。
到目前为止我找到的解决方案分为三类,但其中 none 是理想的:
- 在 运行 编写脚本之前以某种方式手动修复 Python 的模块搜索路径。
- 您可以手动将
mylibsrc
添加到我的 PYTHONPATH
环境变量中。这似乎是最官方的 (Pythonic?) 解决方案,但这意味着每次我签出一个项目时,我必须记住在我可以 运行 任何代码之前手动更改我的环境。
- 将
.
添加到我的 PYTHONPATH
环境变量的开头。据我了解,这可能会有一些安全问题。如果我是唯一使用我的代码的人,这实际上是我最喜欢的技巧,但我不是,我不想让其他人这样做。
- 在互联网上查看答案时,对于
test/
目录中的文件,我看到建议它们都(间接)包含一行代码 sys.path.insert(0, os.path.abspath('..'))
(例如 structuring your project).呸!对于仅用于测试的文件,而不是那些将与软件包一起安装的文件,这似乎是一个可以忍受的 hack。
- 编辑:从那以后我找到了一个替代方案,结果属于这一类:通过 运行 将脚本与 Python 的
-m
脚本、搜索路径从工作目录而不是 bin/
目录开始。有关更多详细信息,请参阅下面的答案。
- 在使用前将包安装到虚拟环境,使用 setup.py(直接 运行 或使用 pip)。
- 如果我只是在测试一个我不确定在语法上是否正确的更改,这似乎有点过分了。我正在从事的一些项目甚至不打算作为包安装,但我想对所有内容使用相同的目录结构,这意味着编写 setup.py 只是为了测试它们!
- 编辑:下面的答案中讨论了这两个有趣的变体:logc 的答案中的
setup.py develop
命令和我的 pip install -e
。他们避免为每个小编辑重新 "install",但您仍然需要为您从未打算完全安装的软件包创建一个 setup.py
,并且与 PyCharm 不能很好地配合使用(它有一个 运行 develop
命令的菜单条目,但没有简单的方法 运行 它复制到虚拟环境的脚本。
- 将脚本移动到项目的根目录(即在
mylibsrc/
而不是 mylibsrc/bin/
中)。
- 呸!这是不得已的办法
,但不幸的是,这似乎是目前唯一可行的选择。
最简单的方法是在你的setup.py
脚本中使用setuptools
,并使用entry_points
关键字,参见Automatic Script Creation的文档。
更详细地说:您创建一个 setup.py
看起来像这样
from setuptools import setup
setup(
# other arguments here...
entry_points={
'console_scripts': [
'foo = my_package.some_module:main_func',
'bar = other_module:some_func',
],
'gui_scripts': [
'baz = my_package_gui:start_func',
]
}
)
然后在 setup.py
所在的目录下创建其他 Python 包和模块,例如按照上面的例子:
.
├── my_package
│ ├── __init__.py
│ └── some_module.py
├── my_package_gui
│ └── __init__.py
├── other_module.py
└── setup.py
然后是运行
$ python setup.py install
或
$ python setup.py develop
无论哪种方式,都会为您创建新的 Python 脚本(不带 .py
后缀的可执行脚本),指向您在 setup.py
中描述的入口点。通常,它们位于 Python 解释器的 "directory where executable binaries should be" 概念中,这通常已经在您的 PATH 中。如果你使用的是虚拟环境,那么 virtualenv
会欺骗 Python 解释器认为这个目录是 bin/
在你定义的 virtualenv 应该在的任何地方。按照上面的示例,在 virtualenv 中,运行ning 前面的命令应该导致:
bin
├── bar
├── baz
└── foo
运行 模块作为脚本
自从我发布这个问题后,我了解到您可以 运行 一个模块,就好像它是一个脚本一样,使用 Python 的 -m
命令行开关(我以为只适用于包裹)。
所以我认为最好的解决方案是:
- 与其在 bin 子目录中编写包装器脚本,不如将大部分逻辑放在模块中(无论如何都应该如此),并像在脚本中一样放在相关模块的末尾
if __name__ == "__main__": main()
.
- 到运行命令行上的脚本,直接这样调用模块:
python -m pkg_name.module_name
- 如果您有 setup.py,正如 Alik 所说,您可以在安装时生成包装脚本,这样您的用户就不需要 运行 他们以这种有趣的方式。
PyCharm 不以这种方式支持 运行ning 模块(参见 this request)。但是,您可以像往常一样只使用 运行 模块(以及 bin 中的脚本),因为 PyCharm 会自动将项目根目录添加到 PYTHONPATH,因此无需任何进一步的努力即可解析导入语句。不过,这有一些问题:
- 主要问题是工作目录不正确,因此无法打开数据文件。不幸的是,没有快速解决办法;第一次 运行 每个脚本时,您必须停止它并更改其配置的工作目录(参见 this link)。
- 如果你的包目录不在项目根目录下,需要在项目结构设置页面将其父目录标记为源目录
- 相对导入不起作用,即您可以
from pkg_name.other_module import fn
但不能 from .other_module import fn
。无论如何,相对导入通常都是糟糕的风格,但它们对单元测试很有用。
- 如果一个模块有循环依赖并且你直接运行它,它最终会被导入两次(一次是
pkg_name.module_name
,一次是__main__
)。但是无论如何你都不应该有循环依赖。
额外的命令行乐趣:
- 如果您仍想在
bin/
中放置一些脚本,您可以使用 python -m bin.scriptname
调用它们(但在 Python 2 中您需要放置一个 __init__.py
在 bin 目录中)。
- 你甚至可以 运行 整个包,如果它有
__main__.py
,像这样:python -m pkg_name
Pip 可编辑模式
命令行有一个替代方法,虽然不那么简单,但仍然值得了解:
- 使用pip的可编辑模式,documented here
- 要使用它,制作一个 setup.py,然后使用以下命令将软件包安装到您的虚拟环境中:
pip install -e .
- 注意结尾的点,它指的是当前目录。
- 这将从您的 setup.py 生成的脚本放在您的虚拟环境的 bin 目录中,并链接到您的包源代码,这样您就可以编辑和调试它而无需重新 运行ning pip。
- 完成后,您可以运行
pip uninstall pkg_name
- 这类似于
setup.py
的develop
命令,但卸载似乎效果更好。
我正在尝试将我的 Python 代码放入用于使用 setup.py
和 PyPI 进行部署的标准目录结构中。对于一个名为 mylib 的 Python 库,它将是这样的:
mylibsrc/
README.rst
setup.py
bin/
some_script.py
mylib/
__init.py__
foo.py
通常还有一个 test/
子目录,但我还没有尝试编写单元测试。在 official Python packaging documentation 中可以找到将脚本放在 bin/
子目录中的建议。
当然,脚本以如下代码开头:
#!/usr/bin/env python
from mylib.foo import something
something("bar")
这在最终部署脚本(例如到 devpi)然后使用 pip 安装它时效果很好。但是如果我直接从源目录 运行 脚本,就像我在对 library/script 开发新的更改时所做的那样,我会得到这个错误:
ImportError: No module named 'mylib'
即使当前工作目录是根目录 mylibsrc/
并且我通过键入 ./bin/some_script.py
运行 脚本也是如此。这是因为 Python 开始在脚本目录 运行 中搜索包(即来自 bin/
),而不是当前工作目录。
什么是在开发包时使 运行 脚本变得容易的永久性好方法?
这是一个relevant other question(特别是对第一个答案的评论)。
到目前为止我找到的解决方案分为三类,但其中 none 是理想的:
- 在 运行 编写脚本之前以某种方式手动修复 Python 的模块搜索路径。
- 您可以手动将
mylibsrc
添加到我的PYTHONPATH
环境变量中。这似乎是最官方的 (Pythonic?) 解决方案,但这意味着每次我签出一个项目时,我必须记住在我可以 运行 任何代码之前手动更改我的环境。 - 将
.
添加到我的PYTHONPATH
环境变量的开头。据我了解,这可能会有一些安全问题。如果我是唯一使用我的代码的人,这实际上是我最喜欢的技巧,但我不是,我不想让其他人这样做。 - 在互联网上查看答案时,对于
test/
目录中的文件,我看到建议它们都(间接)包含一行代码sys.path.insert(0, os.path.abspath('..'))
(例如 structuring your project).呸!对于仅用于测试的文件,而不是那些将与软件包一起安装的文件,这似乎是一个可以忍受的 hack。 - 编辑:从那以后我找到了一个替代方案,结果属于这一类:通过 运行 将脚本与 Python 的
-m
脚本、搜索路径从工作目录而不是bin/
目录开始。有关更多详细信息,请参阅下面的答案。
- 您可以手动将
- 在使用前将包安装到虚拟环境,使用 setup.py(直接 运行 或使用 pip)。
- 如果我只是在测试一个我不确定在语法上是否正确的更改,这似乎有点过分了。我正在从事的一些项目甚至不打算作为包安装,但我想对所有内容使用相同的目录结构,这意味着编写 setup.py 只是为了测试它们!
- 编辑:下面的答案中讨论了这两个有趣的变体:logc 的答案中的
setup.py develop
命令和我的pip install -e
。他们避免为每个小编辑重新 "install",但您仍然需要为您从未打算完全安装的软件包创建一个setup.py
,并且与 PyCharm 不能很好地配合使用(它有一个 运行develop
命令的菜单条目,但没有简单的方法 运行 它复制到虚拟环境的脚本。
- 将脚本移动到项目的根目录(即在
mylibsrc/
而不是mylibsrc/bin/
中)。- 呸!这是不得已的办法
,但不幸的是,这似乎是目前唯一可行的选择。
- 呸!这是不得已的办法
最简单的方法是在你的setup.py
脚本中使用setuptools
,并使用entry_points
关键字,参见Automatic Script Creation的文档。
更详细地说:您创建一个 setup.py
看起来像这样
from setuptools import setup
setup(
# other arguments here...
entry_points={
'console_scripts': [
'foo = my_package.some_module:main_func',
'bar = other_module:some_func',
],
'gui_scripts': [
'baz = my_package_gui:start_func',
]
}
)
然后在 setup.py
所在的目录下创建其他 Python 包和模块,例如按照上面的例子:
.
├── my_package
│ ├── __init__.py
│ └── some_module.py
├── my_package_gui
│ └── __init__.py
├── other_module.py
└── setup.py
然后是运行
$ python setup.py install
或
$ python setup.py develop
无论哪种方式,都会为您创建新的 Python 脚本(不带 .py
后缀的可执行脚本),指向您在 setup.py
中描述的入口点。通常,它们位于 Python 解释器的 "directory where executable binaries should be" 概念中,这通常已经在您的 PATH 中。如果你使用的是虚拟环境,那么 virtualenv
会欺骗 Python 解释器认为这个目录是 bin/
在你定义的 virtualenv 应该在的任何地方。按照上面的示例,在 virtualenv 中,运行ning 前面的命令应该导致:
bin
├── bar
├── baz
└── foo
运行 模块作为脚本
自从我发布这个问题后,我了解到您可以 运行 一个模块,就好像它是一个脚本一样,使用 Python 的 -m
命令行开关(我以为只适用于包裹)。
所以我认为最好的解决方案是:
- 与其在 bin 子目录中编写包装器脚本,不如将大部分逻辑放在模块中(无论如何都应该如此),并像在脚本中一样放在相关模块的末尾
if __name__ == "__main__": main()
. - 到运行命令行上的脚本,直接这样调用模块:
python -m pkg_name.module_name
- 如果您有 setup.py,正如 Alik 所说,您可以在安装时生成包装脚本,这样您的用户就不需要 运行 他们以这种有趣的方式。
PyCharm 不以这种方式支持 运行ning 模块(参见 this request)。但是,您可以像往常一样只使用 运行 模块(以及 bin 中的脚本),因为 PyCharm 会自动将项目根目录添加到 PYTHONPATH,因此无需任何进一步的努力即可解析导入语句。不过,这有一些问题:
- 主要问题是工作目录不正确,因此无法打开数据文件。不幸的是,没有快速解决办法;第一次 运行 每个脚本时,您必须停止它并更改其配置的工作目录(参见 this link)。
- 如果你的包目录不在项目根目录下,需要在项目结构设置页面将其父目录标记为源目录
- 相对导入不起作用,即您可以
from pkg_name.other_module import fn
但不能from .other_module import fn
。无论如何,相对导入通常都是糟糕的风格,但它们对单元测试很有用。 - 如果一个模块有循环依赖并且你直接运行它,它最终会被导入两次(一次是
pkg_name.module_name
,一次是__main__
)。但是无论如何你都不应该有循环依赖。
额外的命令行乐趣:
- 如果您仍想在
bin/
中放置一些脚本,您可以使用python -m bin.scriptname
调用它们(但在 Python 2 中您需要放置一个__init__.py
在 bin 目录中)。 - 你甚至可以 运行 整个包,如果它有
__main__.py
,像这样:python -m pkg_name
Pip 可编辑模式
命令行有一个替代方法,虽然不那么简单,但仍然值得了解:
- 使用pip的可编辑模式,documented here
- 要使用它,制作一个 setup.py,然后使用以下命令将软件包安装到您的虚拟环境中:
pip install -e .
- 注意结尾的点,它指的是当前目录。
- 这将从您的 setup.py 生成的脚本放在您的虚拟环境的 bin 目录中,并链接到您的包源代码,这样您就可以编辑和调试它而无需重新 运行ning pip。
- 完成后,您可以运行
pip uninstall pkg_name
- 这类似于
setup.py
的develop
命令,但卸载似乎效果更好。