首页 > 解决方案 > 无法从通过 pip 和 setuptools 安装的 entry_points 脚本中执行 Python 脚本

问题描述

我按照这些说明创建了一个setup.py可以安装 Python“可执行”脚本的文件。这是我的项目结构:

pkgexec/
  setup.py
  pkgexec/
    __init__.py
    __main__.py
    core.py

根据说明,__main__.py'smain()方法是一个入口点setup.py

from setuptools import setup, find_packages

setup(
    name="pkgexec",
    version="0.2.0",
    packages=find_packages(),
    entry_points={ "console_scripts": ["pkgexec = pkgexec.__main__:main"]},
)

pkgexec/我通过运行从目录中安装了包pip install -e .

到目前为止,一切都按预期工作。

不起作用的是通过这个“可执行”入口点执行 Python 脚本。你看,这个包的全部目的是运行 Python 脚本,从包中导入一堆东西,例如script.py使用包中的功能pkgexec并通过pkgexec“可执行文件”“运行”:

pkgexec script.py -v arg1 arg2

以下是 的简化版本__main__.py

import argparse
import sys

from pkgexec import some_stuff

def main(args=None):
    if args is None:
        args = sys.argv[1:]

    parser = argparse.ArgumentParser()
    parser.add_argument('script', help='script to run via pkgexec')
    parser.add_argument(...)
    cli_args = parser.parse_args()

    print(f'{__name__}: Running script {cli_args.script}')
    exec(open(cli_args.script).read(), globals(), globals())  # <-- ???
    print(f'{__name__}: Done')


if __name__ == '__main__':
    sys.exit(main())

问题:什么都没有发生exec(open(cli_args.script).read())(尝试了和没有, globals(), globals())。该脚本未执行。我在这里做错了什么?

我不喜欢的解决方法:

标签: pythonpipexecsetuptoolsentry-point

解决方案


调用exec(). _ _ 我无法弄清楚runpy标准库中有什么不同,但它确实有效。在我的情况下,解决方案是将exec()调用替换为调用runpy.run_path().

这是修改后的__main__.py脚本进行比较:

import argparse
import os
import runpy
import sys

from pkgexec import some_stuff

def main(args=None):
    if args is None:
        args = sys.argv[1:]

    parser = argparse.ArgumentParser()
    parser.add_argument('script', help='script to run via pkgexec')
    parser.add_argument(...)
    cli_args = parser.parse_args()

    print(f'{__name__}: Running script {cli_args.script}')
    mod = argparse.Namespace(
        **runpy.run_path(cli_args.script,
                         run_name=os.path.basename(cli_args.script)))
    print(f'{__name__}: Done')


if __name__ == '__main__':
    sys.exit(main())

我将文件名作为run_name参数发送给run_path(这样脚本“知道”它的实际值而不是由设置__name__的默认值)。<run_path>runpy

请注意,此解决方案仅适用于要运行的脚本包含if __name__ == '__main__'部分(这正是我想要的)。

更新:可以通过执行幕后操作来实现相同的效果runpycompile()首先是代码,然后是代码exec()

# mod = argparse.Namespace(
#     **runpy.run_path(cli_args.script,
#                      run_name=os.path.basename(cli_args.script)))
code = compile(open(cli_args.script).read(),
               os.path.basename(cli_args.script), 
               'exec')
exec(code)

推荐阅读