无法从通过 pip 和 setuptools 安装的 entry_points 脚本中执行 exec() Python 脚本

Cannot exec() Python script from within entry_points script installed via pip and setuptools

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

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

根据说明,__main__.pymain() 方法是 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"]},
)

我通过 运行ning pip install -e ..

pkgexec/ 目录安装了这个包

到目前为止,一切都按预期进行。

工作是通过这个“可执行”入口点执行 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() 都试过了)。脚本未执行。我在这里做错了什么?

我不喜欢的解决方法:

调用 exec() 时,脚本的全局变量发生 某些事情。我无法弄清楚标准库中的 runpy 有什么不同,但它确实有效。我的解决方案是用 runpy.run_path().

调用替换 exec() 调用

这里是修改后的__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__' 部分(这正是我想要的)。

更新: 可以通过执行 runpy 在后台执行的操作来实现相同的效果:compile() 首先是代码,然后是 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)