| 方法 | 命令/函数 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
subprocess 模块 |
subprocess.run() |
现代、灵活、安全、功能强大,是官方推荐的方式 | 语法相对复杂一点 | 绝大多数情况下的首选,可以获取子进程的输出、状态码,控制输入输出等。 |
os 模块 |
os.system() |
非常简单,一行代码就能执行 | 功能有限,不安全(有命令注入风险),难以获取子进程的输出 | 快速执行简单命令,不关心输出和返回值,且参数完全可控的场景。 |
exec() 函数 |
exec(open('file').read()) |
在同一进程中执行,性能高,内存占用低 | 会覆盖当前进程的命名空间,原脚本执行完后,新脚本的内容会完全替换当前脚本 | 动态加载和执行代码块,或者你想在当前脚本中“嵌入”并运行另一个脚本的逻辑。 |
使用 subprocess 模块 (最推荐)
subprocess 模块是Python用来创建和管理子进程的强大工具。subprocess.run() 是Python 3.5+引入的通用函数,是执行命令的推荐方式。

基本用法:执行脚本并传递参数
假设我们有两个文件:
script_to_call.py (被调用的脚本)
这个脚本会接收命令行参数并打印出来。
# script_to_call.py
import sys
import argparse
# 方法一:直接使用 sys.argv
print("--- 通过 sys.argv 接收参数 ---")
print(f"脚本名称: {sys.argv[0]}")
print(f"所有参数列表: {sys.argv[1:]}")
print(f"第一个参数: {sys.argv[1]}")
print(f"第二个参数: {sys.argv[2]}")
# 方法二:使用 argparse 模块(更健壮,推荐)
print("\n--- 通过 argparse 接收参数 ---")
parser = argparse.ArgumentParser(description="这是一个接收参数的示例脚本。")
parser.add_argument('--name', type=str, required=True, help="你的名字")
parser.add_argument('--age', type=int, help="你的年龄")
parser.add_argument('--verbose', action='store_true', help="是否打印详细信息")
args = parser.parse_args()
print(f"你好, {args.name}!")
if args.age:
print(f"你今年 {args.age} 岁了。")
if args.verbose:
print("详细模式已开启。")
main_script.py (主调用脚本)
这个脚本会调用上面的脚本并传递参数。
# main_script.py
import subprocess
import sys
# 定义要执行的命令和参数
# 命令是一个列表,每个元素都是一个独立的参数,这样可以避免shell注入的风险
command = [
'python',
'script_to_call.py',
'--name', 'Alice',
'--age', '30',
'--verbose'
]
# 使用 subprocess.run() 执行命令
# capture_output=True 会捕获标准输出和标准错误
# text=True 会将输出解码为文本
# check=True 如果子进程返回非零状态码(即出错),则会抛出 CalledProcessError 异常
try:
print(f"正在执行命令: {' '.join(command)}")
result = subprocess.run(command, capture_output=True, text=True, check=True)
# 打印子进程的标准输出
print("\n--- 子进程的标准输出 ---")
print(result.stdout)
# 打印子进程的返回码 (正常情况下为0)
print(f"\n子进程返回码: {result.returncode}")
except subprocess.CalledProcessError as e:
print(f"执行命令时出错!返回码: {e.returncode}")
print(f"错误输出:\n{e.stderr}")
except FileNotFoundError:
print("错误: 未找到 'python' 命令或 'script_to_call.py' 文件,请确保它们在当前目录下。")
如何运行: 在终端中,确保两个文件在同一个目录下,然后运行主脚本:

python main_script.py
预期输出:
正在执行命令: python script_to_call.py --name Alice --age 30 --verbose
--- 子进程的标准输出 ---
--- 通过 sys.argv 接收参数 ---
脚本名称: script_to_call.py
所有参数列表: ['--name', 'Alice', '--age', '30', '--verbose']
第一个参数: --name
第二个参数: Alice
--- 通过 argparse 接收参数 ---
你好, Alice!
你今年 30 岁了。
详细模式已开启。
子进程返回码: 0
subprocess.run() 的关键参数
args: 要执行的命令,推荐使用列表形式,因为它更安全(可以防止shell注入),也更清晰。capture_output=True: 捕获标准输出和标准错误,如果设为False(默认),输出会直接显示在终端。text=True: 将捕获到的输出作为字符串返回,如果为False(默认),输出会是字节流。check=True: 如果子进程返回非零退出码(表示出错),则抛出CalledProcessError异常,如果设为False(默认),即使出错也会正常返回。shell=False: 是否通过shell执行,默认为False,设为True可以使用shell特性(如管道 ),但有安全风险。input: 向子进程的标准输入传递字符串或字节。
使用 os 模块
os.system() 是一个非常老派的函数,简单直接,但功能和安全方面都不如 subprocess。
main_script_os.py
# main_script_os.py
import os
# 命令以字符串形式给出
# 注意:这种方式有命令注入风险,如果参数来自用户输入,非常危险!
command_str = "python script_to_call.py --name Bob --age 25"
print(f"正在执行命令: {command_str}")
return_code = os.system(command_str)
print(f"\n命令执行完毕,返回码: {return_code}")
如何运行:

python main_script_os.py
预期输出:
正在执行命令: python script_to_call.py --name Bob --age 25
--- 通过 sys.argv 接收参数 ---
脚本名称: script_to_call.py
所有参数列表: ['--name', 'Bob', '--age', '25']
第一个参数: --name
第二个参数: Bob
--- 通过 argparse 接收参数 ---
你好, Bob!
你今年 25 岁了。
命令执行完毕,返回码: 0
缺点:
- 不安全:如果参数来自用户输入,
name = input("请输入名字: "),恶意用户可以输入rm -rf /,导致command_str变成python script_to_call.py --name rm -rf /,造成严重破坏。 - 难以获取输出:
os.system()只返回命令的退出码,无法获取标准输出或标准错误的内容。
使用 exec() 函数
exec() 会在当前进程中执行代码,而不是创建一个新的子进程,这会带来巨大的副作用。
main_script_exec.py
# main_script_exec.py
print("这是主脚本开始执行的地方。")
# 准备要执行的代码
# 注意:这里我们直接读取并执行文件内容,而不是传参
# 传参需要更复杂的动态构造代码字符串的方式
code_to_run = """
# 这是一个动态执行的代码块
print("这是通过 exec() 执行的代码块。")
import sys
print(f"在 exec 中的 sys.argv: {sys.argv}") # sys.argv 会是调用 exec 的脚本的参数
"""
print("\n--- 即将通过 exec() 执行代码 ---")
exec(code_to_run)
print("\n这是主脚本执行结束的地方。")
# 注意:在执行完 exec 后,如果被 exec 的代码定义了变量,这些变量会存在于当前命名空间中
如何运行:
python main_script_exec.py
预期输出:
这是主脚本开始执行的地方。
--- 即将通过 exec() 执行代码 ---
这是通过 exec() 执行的代码块。
在 exec 中的 sys.argv: ['main_script_exec.py']
这是主脚本执行结束的地方。
重要警告:
exec() 会完全污染当前脚本的命名空间,如果你执行的外部文件定义了函数或变量,它们会覆盖当前脚本的同名函数或变量,且在 exec 执行后依然存在。
script_with_definitions.py
def greet(name):
print(f"Hello from the external script, {name}!")
VERSION = "1.0"
main_script_exec_dangerous.py
import sys
# 被调用的脚本
script_path = 'script_with_definitions.py'
# 动态读取并执行
with open(script_path, 'r') as f:
code = f.read()
print("主脚本中定义的函数:")
def greet(name):
print(f"Hello from the main script, {name}!")
VERSION = "original"
print(f"主脚本中的 VERSION: {VERSION}")
greet("Main User")
print("\n--- 执行外部脚本 ---")
exec(code)
print("\n执行外部脚本后:")
print(f"当前的 VERSION: {VERSION}") # 注意,VERSION 被覆盖了!
greet("Main User Again") # 注意,greet 函数也被覆盖了!
运行 main_script_exec_dangerous.py 会看到 VERSION 和 greet 函数都被外部脚本覆盖了,这就是为什么 exec 通常用于执行你完全信任的、动态生成的代码片段,而不是执行一个独立的、可能存在冲突的脚本。
总结与最佳实践
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 绝大多数情况 | subprocess.run() |
功能最全、最安全、最灵活,可以获取输出、处理错误、控制输入输出,是现代Python的标准做法。 |
| 快速执行、简单命令 | os.system() |
代码最简单,但只适用于参数完全由你控制、不关心输出的情况。不推荐用于生产环境。 |
| 在同一进程中运行逻辑 | exec() |
性能高,但会污染和覆盖当前命名空间,风险极高,除非有特殊需求(如动态加载插件),否则应避免使用。 |
最终建议: 除非有非常特殊的需求,否则请始终使用 subprocess.run() 来调用其他Python脚本或外部命令,它为你提供了对子进程的完全、安全且强大的控制。
