如何通过subprocess.run正确调用Nuitka多入口打包的可执行文件以指定运行对应脚本
我理解你的场景:用Nuitka把a.py和b.py打包成了单个a.exe,通过mklink做b.exe软链接时直接运行完全正常,但用subprocess调用时,无论怎么传参都只跑a的逻辑,而且你不想依赖软链接——毕竟分发时软链接的限制太多,太麻烦。
这问题的核心是:Nuitka靠sys.argv[0]的文件名来匹配打包时指定的主脚本(比如a.exe对应a.py,b.exe对应b.py),但subprocess的默认调用方式会让sys.argv[0]始终指向a.exe的实际路径,导致Nuitka永远只会触发a.py的逻辑。
下面给你几个实用的解决方案,从不需要软链接的底层方法到自定义打包逻辑都有:
方案一:用Windows API直接控制argv[0](无软链接)
这种方法绕开subprocess的限制,直接调用Windows底层的CreateProcessW API,让Nuitka误以为是b.exe在运行,完全不需要创建任何软链接。
修改你的run.py成这样:
import ctypes from ctypes import wintypes # 封装Windows的CreateProcessW API kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) CREATE_NO_WINDOW = 0x08000000 def run_as(exe_full_path, fake_exe_name, args=None): args = args or [] # 构造命令行:第一个元素是我们要模拟的文件名(比如b.exe),后面跟着参数 cmd_parts = [fake_exe_name] + args cmd_line = ' '.join(f'"{p}"' if ' ' in p else p for p in cmd_parts) startup_info = wintypes.STARTUPINFO() process_info = wintypes.PROCESS_INFORMATION() startup_info.cb = ctypes.sizeof(startup_info) # 调用API:指定实际运行的a.exe路径,同时传入带fake文件名的命令行 success = kernel32.CreateProcessW( wintypes.LPCWSTR(exe_full_path), wintypes.LPWSTR(cmd_line), None, None, False, CREATE_NO_WINDOW, None, None, ctypes.byref(startup_info), ctypes.byref(process_info) ) if not success: raise ctypes.WinError(ctypes.get_last_error()) # 等待进程结束并清理资源 kernel32.WaitForSingleObject(process_info.hProcess, -1) exit_code = wintypes.DWORD() kernel32.GetExitCodeProcess(process_info.hProcess, ctypes.byref(exit_code)) kernel32.CloseHandle(process_info.hProcess) kernel32.CloseHandle(process_info.hThread) return exit_code.value # 调用示例 if __name__ == "__main__": # 模拟a.exe运行,传参数1 run_as(r"C:\Users\sl\work\tests\so2\a.exe", "a.exe", ["1"]) # 模拟b.exe运行,传参数2 run_as(r"C:\Users\sl\work\tests\so2\a.exe", "b.exe", ["2"])
打包run.exe后运行,就能得到你想要的输出:
a: ['a.exe', '1'] b: ['b.exe', '2']
方案二:自定义入口脚本(更灵活,有跨平台潜力)
放弃Nuitka的--main多脚本参数,自己写一个入口分发器,根据sys.argv[0]或自定义参数来决定运行哪个脚本,这样打包和调用都更可控。
步骤1:写统一入口entry.py
import sys import os import importlib.util def run_script(script_path): # 动态导入并运行指定的脚本,保持原脚本的__main__上下文 spec = importlib.util.spec_from_file_location("__main__", script_path) module = importlib.util.module_from_spec(spec) sys.modules["__main__"] = module spec.loader.exec_module(module) def main(): # 取启动文件名的前缀判断要运行哪个脚本 exe_basename = os.path.basename(sys.argv[0]).lower() if exe_basename.startswith("b"): run_script("b.py") else: run_script("a.py") if __name__ == "__main__": main()
步骤2:打包entry.exe
python -m nuitka --lto=no --onefile --follow-imports entry.py
步骤3:修改run.py调用
import subprocess import os # 运行a.py逻辑:直接调用entry.exe subprocess.run(["./entry.exe", "1"]) # 运行b.py逻辑:让argv[0]是b.exe,指定executable为entry.exe subprocess.run(["b.exe", "2"], executable="./entry.exe")
这种方式下,哪怕b.exe不存在,只要sys.argv[0]是b.exe,entry.py就会跑b.py的逻辑,完全不用软链接。
方案三:软链接直接调用(适合内部场景)
如果是内部使用、不用考虑复杂分发的场景,最简单的方式就是直接在subprocess里调用软链接:
import subprocess subprocess.run(["./a.exe", "1"]) subprocess.run(["./b.exe", "2"])
但要注意Windows下创建软链接需要管理员权限,而且分发到其他机器时,如果路径变了软链接会失效,只适合小范围使用。
备注:内容来源于stack exchange,提问作者Stuart Lynne

