1. 什么是进程? #
1.1 什么是进程? #
进程就是正在运行的程序。你打开一个记事本,就启动了一个进程;运行 Python 脚本,也启动了一个进程。每个进程有自己独立的内存空间,互不干扰。
1.2 为什么需要在 Python 里调用其他程序? #
有时我们需要在 Python 中执行系统命令或其他程序,例如:
- 执行
ping检查网络是否连通 - 执行
dir列出当前目录文件 - 调用其他可执行程序(如编译器、打包工具等)
如果不用 subprocess,就只能用老式的 os.system(),功能弱、不安全。subprocess 提供了统一、安全、灵活的方式来创建和管理子进程。
通俗比喻:你的 Python 程序是"主程序",通过 subprocess 可以"派手下"去执行别的程序,等它执行完再拿回结果。
2. 什么是 subprocess? #
subprocess 是 Python 标准库自带的模块,用于:
- 创建子进程:在 Python 中启动另一个程序
- 获取输出:读取子进程打印到屏幕的内容
- 控制输入:向子进程发送数据
- 获取返回码:判断命令是否执行成功(通常 0 表示成功)
下面我们从最常用的 run() 函数开始学习。
3. 最简单的用法:subprocess.run() #
run() 是最推荐的用法:执行命令,等待执行完成,然后返回结果。适合绝大多数场景。
3.1 执行命令(不捕获输出) #
最简单的用法:执行一条命令,输出直接显示在终端。
# 导入 subprocess 模块
import subprocess
# 执行 dir 命令列出当前目录(Windows)
# 参数用列表形式:[程序名, 参数1, 参数2, ...]
# cmd /c 表示执行完命令后关闭窗口
subprocess.run(["cmd", "/c", "dir"])说明:在 Windows 下,dir 是 cmd 的内置命令,所以需要写成 ["cmd", "/c", "dir"]。输出会直接打印到你的终端。
3.2 执行命令并捕获输出 #
如果想把命令的输出保存到变量里,而不是打印到屏幕,需要加上 capture_output=True 和 text=True。
# 导入 subprocess 模块
import subprocess
# 执行 dir 命令,并捕获标准输出和标准错误
# capture_output=True 表示把输出收集起来,不打印到屏幕
# text=True 表示以字符串形式返回,否则是字节串
result = subprocess.run(
["cmd", "/c", "dir"],
capture_output=True,
text=True
)
# 标准输出(命令正常打印的内容)
print(result.stdout)
# 标准错误(命令报错时打印的内容)
print(result.stderr)
# 返回码:0 表示成功,非 0 表示失败
print("返回码:", result.returncode)3.3 带超时和错误检查 #
可以设置超时时间(秒),超时则自动终止;也可以设置 check=True,命令失败时自动抛出异常。
# 导入 subprocess 模块
import subprocess
# 执行 ping 命令,最多等 5 秒
# timeout=5 表示超过 5 秒就终止
# check=True 表示返回码非 0 时抛出 CalledProcessError
try:
result = subprocess.run(
["ping", "-n", "2", "127.0.0.1"],
capture_output=True,
text=True,
timeout=5,
check=True
)
# 打印 ping 的输出
print(result.stdout)
except subprocess.TimeoutExpired:
# 超时异常
print("命令执行超时")
except subprocess.CalledProcessError as e:
# 命令执行失败(返回码非 0)
print(f"命令失败,返回码: {e.returncode}")说明:ping -n 2 表示 ping 2 次(Windows 用 -n,Linux 用 -c)。127.0.0.1 是本机地址,一般都能 ping 通。
4. run() 常用参数速查 #
| 参数 | 作用 |
|---|---|
args |
要执行的命令,推荐用列表如 ["cmd", "/c", "dir"] |
capture_output=True |
捕获标准输出和标准错误,不打印到屏幕 |
text=True |
输出以字符串形式返回,否则是字节串 |
timeout=秒数 |
超时时间,超时则终止子进程 |
check=True |
返回码非 0 时抛出 CalledProcessError |
shell=True |
通过 shell 执行(不推荐,有安全风险) |
提示:初学只需掌握 args、capture_output、text 即可,其他按需查阅。
5. 常见用法示例 #
5.1 执行 Python 代码并获取输出 #
用 subprocess 调用 Python 自身执行一段代码,并拿到输出。这种方式跨平台,Windows 和 Linux 都能运行。
# 导入 subprocess 和 sys
import subprocess
import sys
# 用当前 Python 解释器执行一段代码
# sys.executable 是当前 Python 的路径
# -c 表示后面跟的是要执行的代码字符串
result = subprocess.run(
[sys.executable, "-c", "print('Hello from subprocess!'); print(1+2)"],
capture_output=True,
text=True
)
# 打印子进程的输出
print("子进程输出:", result.stdout)
# 打印返回码
print("返回码:", result.returncode)5.2 把输出写入文件 #
不想要输出显示在屏幕,可以把它重定向到文件。
# 导入 subprocess 和 sys
import subprocess
import sys
# 以写入模式打开文件
with open("output.txt", "w", encoding="utf-8") as f:
# stdout=f 表示把标准输出写入文件 f
# stderr=subprocess.STDOUT 表示把错误也合并到 stdout
subprocess.run(
[sys.executable, "-c", "print('这条内容会写入文件')"],
stdout=f,
stderr=subprocess.STDOUT
)
# 读取并打印文件内容
with open("output.txt", "r", encoding="utf-8") as f:
print("文件内容:", f.read())5.3 丢弃输出(不关心命令打印什么) #
如果只关心命令有没有执行成功,不关心输出内容,可以丢弃输出。
# 导入 subprocess 和 sys
import subprocess
import sys
# DEVNULL 表示丢弃,相当于输出到"黑洞"
# 命令会执行,但不会在屏幕显示,也不会保存
subprocess.run(
[sys.executable, "-c", "print('你看不到我')"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# 程序正常执行完毕
print("命令已执行(输出已丢弃)")6. Popen:不等待、边执行边读取 #
run() 会一直等到命令执行完才返回。如果希望边执行边读取输出(例如实时看日志),需要用 Popen。
6.1 基本用法:启动进程并读取输出 #
# 导入 subprocess 和 sys
import subprocess
import sys
# 用 Popen 启动子进程,不等待它结束
# stdout=subprocess.PIPE 表示创建管道,可以读取输出
# stderr=subprocess.STDOUT 表示把错误合并到标准输出
# text=True 表示按字符串读取
# bufsize=1 表示行缓冲,每行输出后就能读到
proc = subprocess.Popen(
[sys.executable, "-c", "import time; [print(i) or time.sleep(0.5) for i in range(5)]"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
# 逐行读取输出,实现"实时打印"
for line in proc.stdout:
# 打印每一行(end='' 避免重复换行)
print(line, end="")
# 立即刷新,确保及时显示
sys.stdout.flush()
# 等待子进程结束
proc.wait()
# 打印返回码
print("返回码:", proc.returncode)说明:子进程会每秒打印一个数字(0 到 4),主进程边读边打印,实现"实时输出"效果。
6.2 向子进程发送输入(communicate) #
如果子进程需要从标准输入读取数据,可以用 communicate() 发送。
# 导入 subprocess 和 sys
import subprocess
import sys
# 启动一个子进程,它会从标准输入读取并打印
# 子进程:读取输入,转成大写后打印
proc = subprocess.Popen(
[sys.executable, "-c", "import sys; print(sys.stdin.read().upper())"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# 向子进程发送数据,并等待它结束,返回 (stdout, stderr)
out, err = proc.communicate(input="hello world")
# 打印子进程的输出(HELLO WORLD)
print("子进程输出:", out)
# 打印返回码
print("返回码:", proc.returncode)7. 安全注意事项:慎用 shell=True #
7.1 为什么不要用 shell=True? #
当 shell=True 时,命令会交给系统 shell(如 cmd)解析。如果命令里包含用户输入,恶意用户可能注入额外命令,造成安全风险。
# 危险示例:不要模仿!
# 如果 user_input 来自用户输入,可能被注入恶意命令
import subprocess
# 模拟恶意输入
user_input = "hello & dir"
# 危险:shell 会解析 &,可能执行多条命令
# subprocess.run(f"echo {user_input}", shell=True) # 不要这样做7.2 安全做法:用列表传参 #
推荐:始终用列表形式传参,参数会原样传给程序,不会被 shell 解析。
# 导入 subprocess 和 sys
import subprocess
import sys
# 安全:用列表传参,即使用户输入特殊字符也会被当作普通参数
user_input = "hello & dir"
# 每个参数独立传递,不会被 shell 解析
subprocess.run([sys.executable, "-c", f"print({repr(user_input)})"])提示:除非你明确需要 shell 特性(如通配符 *),否则不要用 shell=True。
8. 常见异常 #
| 异常 | 何时抛出 |
|---|---|
subprocess.CalledProcessError |
check=True 且命令返回非 0 |
subprocess.TimeoutExpired |
命令超过 timeout 秒未结束 |
FileNotFoundError |
命令对应的程序不存在 |
处理方式:用 try/except 捕获即可,前面 3.3 节已有示例。
9. 总结 #
| 需求 | 推荐方法 |
|---|---|
| 执行命令并获取输出 | subprocess.run(..., capture_output=True, text=True) |
| 执行命令,不关心输出 | subprocess.run(..., stdout=DEVNULL, stderr=DEVNULL) |
| 需要超时或失败时抛异常 | 加上 timeout= 和 check=True |
| 边执行边读取输出 | subprocess.Popen + 循环读取 proc.stdout |
| 向子进程发送输入 | Popen + communicate(input=...) |
记忆口诀:日常用 run(),要实时输出用 Popen;传参用列表,别用 shell=True。