在标准项目目录结构中调用脚本(bin子目录的Python路径)

2024-10-01 17:27:47 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试将Python代码放入标准目录结构中,用于使用setup.py和PyPI进行部署。对于一个名为mylib的Python库,它应该是这样的:

mylibsrc/
  README.rst
  setup.py
  bin/
    some_script.py
  mylib/
    __init.py__
    foo.py

通常还有一个test/子目录,但我还没有尝试编写单元测试。在bin/子目录中包含脚本的建议可以在official Python packaging documentation中找到。在

当然,脚本以如下代码开头:

^{pr2}$

当最终部署脚本(例如,部署到devpi),然后用pip安装它时,这种方法非常有效。但是,如果我直接从源目录运行脚本,就像我在开发库/脚本的新更改一样,我会得到以下错误:

ImportError: No module named 'mylib'

即使当前工作目录是根mylibsrc/,并且我通过键入./bin/some_script.py运行脚本,也是如此。这是因为Python开始在正在运行的脚本的目录(即从bin/)中搜索包,而不是在当前工作目录中。在

在开发包时,什么是一种好的、永久的方法来简化脚本的运行?在

这里有一个relevant other question(尤其是对第一个答案的注释)。在

到目前为止,我找到的解决方案分为三类,但都不理想:

  1. 在运行脚本之前,以某种方式手动修复Python的模块搜索路径。
    • 您可以手动将mylibsrc添加到我的PYTHONPATH环境变量中。这似乎是最正式的(Python?)解决方案,但这意味着每次我签出一个项目时,我必须记住在我可以在其中运行任何代码之前手动更改我的环境。在
    • .添加到我的PYTHONPATH环境变量的开头。据我所知,这可能会有一些安全问题。如果我是唯一一个使用我的代码的人,这将是我最喜欢的技巧,但我不是,我也不想让别人这么做。在
    • 当我在互联网上查找答案时,对于test/目录中的文件,我看到了一些建议,它们都(间接地)包含一行代码sys.path.insert(0, os.path.abspath('..'))(例如在structuring your project)中)。讨厌!对于那些只用于测试的文件,而不是那些将与软件包一起安装的文件,这似乎是一种可以忍受的黑客攻击。在
    • 编辑:我后来发现了一个替代方法,结果是属于这个类别:通过使用Python的-m脚本运行脚本,搜索路径从工作目录开始,而不是从bin/目录开始。更多细节请看我的回答。在
  2. 在使用包之前,请使用设置.py(直接运行或使用pip)。
    • 如果我只是在测试一个我还不确定语法是否正确的更改,那么这似乎有点过头了。我正在处理的一些项目甚至不打算作为包安装,但我希望对所有项目使用相同的目录结构,这意味着要编写一个设置.py只是为了让我能测试他们!在
    • 编辑:下面的答案讨论了两个有趣的变体:logc答案中的setup.py develop命令和我的pip install -e。它们避免了每次编辑都要重新“安装”,但是您仍然需要为您从未打算完全安装的包创建一个setup.py,并且不能很好地与PyCharm一起工作(PyCharm有一个菜单项来运行develop命令,但是没有简单的方法来运行它复制到虚拟环境中的脚本)。在
  3. 将脚本移动到项目的根目录(即在mylibsrc/而不是mylibsrc/bin/)中。
    • 恶心!这是最后的手段,但不幸的是,这似乎是目前唯一可行的选择。在

Tags: pip文件项目方法答案代码py目录
2条回答

将模块作为脚本运行

自从我发布这个问题以来,我了解到可以像运行脚本一样使用Python的-m命令行开关(我原以为它只适用于包)来运行模块。在

所以我认为最好的解决办法是:

  • 不要在bin子目录中编写包装器脚本,而是将大部分逻辑放在模块中(无论如何都应该这样),并像在脚本中那样将大部分逻辑放在相关模块的末尾if __name__ == "__main__": main()。在
  • 要在命令行上运行脚本,请像这样直接调用模块:python -m pkg_name.module_name
  • 如果你有设置.py,正如Alik所说,您可以在安装时生成包装器脚本,这样您的用户就不需要以这种有趣的方式运行它们。在

PyCharm不支持以这种方式运行模块(请参见this request)。但是,您可以像normal一样运行模块(以及bin中的脚本),因为PyCharm会自动将项目根添加到PYTHONPATH中,因此import语句无需进一步处理就可以解析。不过,这里有几个问题:

  • 主要问题是工作目录不正确,因此打开数据文件将无法工作。不幸的是,没有快速修复方法;第一次运行每个脚本时,必须停止它并更改其配置的工作目录(请参见this link)。在
  • 如果包目录不直接在根项目目录中,则需要在“项目结构设置”页中将其父目录标记为源目录。在
  • 相对导入不起作用,也就是说,你可以做from pkg_name.other_module import fn,但不能from .other_module import fn。相对导入通常是糟糕的风格,但它们对单元测试很有用。在
  • {{cd6}作为一个循环的依赖项,{它将直接运行两次。但是无论如何你不应该有循环依赖。在

额外的命令行乐趣:

  • 如果您仍然想在bin/中放置一些脚本,可以使用python -m bin.scriptname调用它们(但是在python2中,您需要在bin目录中放一个__init__.py)。在
  • 您甚至可以运行整个包,如果它有一个__main__.py,像这样:python -m pkg_name

Pip可编辑模式

命令行还有一种替代方法,虽然不那么简单,但仍然值得了解:

  • 使用pip的可编辑模式documented here
  • 要使用它,做一个设置.py,并使用以下命令将包安装到虚拟环境中:pip install -e .
  • 注意后面的点,它表示当前目录。在
  • 这会将从设置.py在虚拟环境的bin目录中,并链接到包源代码,这样您就可以编辑和调试它,而无需重新运行pip。在
  • 完成后,可以运行pip uninstall pkg_name
  • 这类似于setup.pydevelop命令,但卸载似乎能更好地工作。在

最简单的方法是在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包和模块,例如,按照上面的示例:

^{pr2}$

然后跑

$ python setup.py install

或者

$ python setup.py develop

不管怎样,都会为您创建新的Python脚本(不带.py后缀的可执行脚本),这些脚本指向您在setup.py中描述的入口点。通常,它们位于Python解释器的“可执行二进制文件所在的目录”的概念中,这通常已经在您的路径上了。如果您使用的是虚拟环境,那么virtualenv会诱使Python解释器认为该目录是bin/,位于您定义virtualenv的任何地方。按照上面的示例,在virtualenv中,运行前面的命令应该会导致:

bin
├── bar
├── baz
└── foo

相关问题 更多 >

    热门问题