使用 os.system() (最简单,但功能有限)
这是最直接、最简单的方法,它就像在系统的命令行终端中直接输入命令一样。

工作原理:os.system() 会启动一个子 shell(在 Windows 上是 cmd.exe,在 Linux/macOS 上是 /bin/sh)来执行你传入的字符串命令。
示例代码:
script_to_call.py (被调用的脚本)
# 这是一个简单的被调用脚本
import sys
import time
print("被调用脚本开始执行...")
print(f"接收到的参数数量: {len(sys.argv)}")
for i, arg in enumerate(sys.argv):
print(f"参数 {i}: {arg}")
time.sleep(2) # 模拟耗时操作
print("被调用脚本执行完毕!")
main_script.py (主调用脚本)
import os
print("主脚本开始...")
# 构建要执行的命令字符串
# 注意:在 Windows 上使用 python,在 Linux/macOS 上使用 python3
command = 'python script_to_call.py arg1 "arg with space"'
print(f"准备执行命令: {command}")
# 执行命令
return_code = os.system(command)
print(f"\n命令执行完毕,返回码: {return_code}")
print("主脚本结束。")
如何运行:
# 在终端中运行主脚本 python main_script.py
输出:
主脚本开始...
准备执行命令: python script_to_call.py arg1 "arg with space"
被调用脚本开始执行...
接收到的参数数量: 3
参数 0: script_to_call.py
参数 1: arg1
参数 2: arg with space
被调用脚本执行完毕!
命令执行完毕,返回码: 0
主脚本结束。
优点:
- 非常简单,一行代码就能搞定。
- 适用于简单的、独立的脚本调用。
缺点:
- 无法直接获取子脚本的输出:
os.system()只能返回命令的退出码(return_code),无法捕获print或sys.stdout的输出。 - 性能较差:需要创建一个完整的子 shell 进程,开销较大。
- 跨平台兼容性:路径分隔符(
\vs )和命令格式在不同操作系统上可能需要调整。 - 安全性:如果命令字符串来自用户输入,容易导致命令注入漏洞。
使用 subprocess 模块 (推荐,功能最强大)
subprocess 是 Python 官方推荐的用于创建和管理子进程的模块,它旨在替代 os.system()、os.spawn* 等旧方法,提供了更强大、更灵活、更安全的接口。
subprocess 有多种函数,最常用的是 run()。
1 subprocess.run() (推荐用法)
这是 Python 3.5+ 引入的现代、推荐的接口。
示例代码:
main_script.py (使用 subprocess.run)
import subprocess
print("主脚本开始...")
# 要执行的命令,推荐使用列表形式,更安全
command = ['python', 'script_to_call.py', 'arg1', 'arg with space']
# 使用 run() 函数执行
# capture_output=True: 捕获标准输出和标准错误
# text=True: 将输出解码为文本(字符串),否则是字节
# check=True: 如果子进程返回非零退出码(表示出错),则抛出 CalledProcessError 异常
try:
result = subprocess.run(command, capture_output=True, text=True, check=True, timeout=5)
print("\n--- 调用成功 ---")
print(f"返回码: {result.returncode}")
print("标准输出:")
print(result.stdout) # result.stdout 是一个字符串
except subprocess.CalledProcessError as e:
print(f"\n--- 调用失败,返回码 {e.returncode} ---")
print("标准错误:")
print(e.stderr)
except subprocess.TimeoutExpired:
print("\n--- 调用超时 ---")
print("主脚本结束。")
优点:
- 功能强大:可以轻松捕获标准输出、标准错误、返回码。
- 安全性高:推荐使用列表形式传递命令和参数,可以避免 shell 注入问题。
- 灵活性高:可以设置超时、工作目录、环境变量等。
- 跨平台性好。
缺点:
- 相比
os.system()代码稍多,但这是为了换取强大功能。
2 subprocess.Popen() (最底层,最灵活)
Popen 是 subprocess 的底层构造函数,run() 也是基于它封装的,当你需要更精细地控制子进程的生命周期时(与子进程进行实时交互),就需要使用 Popen。
示例代码:
main_script.py (使用 subprocess.Popen)
import subprocess
import sys
print("主脚本开始...")
command = ['python', 'script_to_call.py', 'arg1', 'arg with space']
# 使用 Popen 启动进程
# stdout=subprocess.PIPE: 捕获标准输出
# stderr=subprocess.PIPE: 捕获标准错误
# text=True: 以文本模式处理流
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 可以在这里做其他事情,等待子进程完成
print("子进程已启动,主脚本正在等待其完成...")
# 等待进程结束,并获取输出和返回码
# communicate() 会读取所有输出并等待进程结束
stdout, stderr = process.communicate()
print("\n--- 调用结束 ---")
print(f"返回码: {process.returncode}")
print("标准输出:")
print(stdout)
if stderr:
print("标准错误:")
print(stderr)
print("主脚本结束。")
优点:
- 极致的灵活性:可以启动进程后立即返回,然后通过
poll()检查状态,或者通过communicate()、stdin.write()等方法与子进程进行双向交互。 - 适合需要复杂交互或并发控制的场景。
缺点:
- 使用比
run()更复杂,容易出错。
将脚本作为模块导入
如果你的两个脚本紧密相关,并且你希望共享变量和函数,而不是像两个独立程序那样运行,那么导入模块是最佳选择。
工作原理:直接将另一个脚本作为 Python 模块导入,然后调用其中的函数或直接执行其顶层代码。
示例代码:
script_to_call.py (被调用的模块)
# 这是一个可以被导入的模块
print("模块 script_to_call.py 被导入。")
# 定义一些可以被调用的函数
def greet(name):
"""一个简单的问候函数"""
return f"你好, {name}! 欢迎使用模块功能。"
# 脚本顶层代码,在被导入时也会执行
if __name__ == "__main__":
# 这部分代码只有在直接运行此脚本时才会执行
# 如果是被导入,则不会执行
print("script_to_call.py 作为主程序直接运行。")
print("接收到的参数:", sys.argv[1:])
main_script.py (主调用脚本)
import sys
import script_to_call # 直接导入模块
print("主脚本开始...")
# 调用被导入模块的函数
message = script_to_call.greet("Alice")
print(message)
# 被导入模块的顶层代码(if __name__ == "__main__" 之外的部分)在导入时就已经执行了
# 所以你会在主脚本的开头看到 "模块 script_to_call.py 被导入。"
print("主脚本结束。")
如何运行:
python main_script.py
输出:
模块 script_to_call.py 被导入。
主脚本开始...
你好, Alice! 欢迎使用模块功能。
主脚本结束。
优点:
- 代码复用性高:可以共享函数、类和变量。
- 性能好:没有创建新进程的开销。
- 调试方便:可以在同一个 IDE 中调试所有代码。
缺点:
- 耦合度高:两个脚本不再是独立的,修改
script_to_call.py的顶层逻辑可能会影响main_script.py。 - 不适合独立任务:
script_to_call.py是一个完整的、应该独立运行的任务(例如数据处理、训练模型),将其导入会破坏其独立性。
总结与如何选择
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
os.system() |
极其简单 | 功能弱,无法获取输出,不安全,性能差 | 快速测试,调用非常简单的、无参数的独立脚本。不推荐用于生产环境。 |
subprocess.run() |
功能强大,安全,灵活,可获取输出/错误/返回码 | 代码稍多 | 强烈推荐,绝大多数需要调用外部脚本或命令的场景,尤其是需要传递参数和获取结果时。 |
subprocess.Popen() |
最灵活,可交互,并发控制 | 复杂,易用性低 | 需要与子进程进行实时I/O交互(如SSH连接、游戏引擎),或需要精细管理多个子进程时。 |
| 模块导入 | 代码复用,高性能,耦合 | 耦合度高,破坏脚本独立性 | 两个脚本属于同一个项目,功能紧密相关,需要共享函数和变量的情况。 |
决策建议:
- 如果你只是想快速运行一个独立的脚本,不关心结果:可以用
os.system(),但请优先考虑更现代的方式。 - 如果你需要传递参数,并且需要知道脚本是否成功执行,或者需要获取它的输出结果:请使用
subprocess.run(),这是最通用、最推荐的方案。 - 如果你需要一边运行子脚本,一边实时处理它的输出:使用
subprocess.Popen()。 - 如果你的两个脚本本质上就是同一个程序的两个部分,它们需要共享代码和数据:使用模块导入的方式。
