Python subprocess与Popen

subprocess

stdin stdout stderr

subprocess.run(command, check=True, text=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)subprocess.run(command, check=True, text=True) 区别在哪?

主要区别如下:

  1. 输入输出重定向

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 默认方式(不指定)
    # 继承父进程的标准输入、输出、错误流
    subprocess.run(command, check=True, text=True)

    # 指定 stdin/stdout/stderr
    subprocess.run(
    command,
    check=True,
    text=True,
    stdin=subprocess.PIPE, # 重定向标准输入
    stdout=subprocess.PIPE, # 重定向标准输出
    stderr=subprocess.PIPE # 重定向标准错误
    )
  2. 主要不同点

    • stdin=subprocess.PIPE:子进程的标准输入被重定向到一个管道
    • stdout=subprocess.PIPE:子进程的标准输出被捕获到一个管道
    • stderr=subprocess.PIPE:子进程的标准错误输出被捕获到一个管道
  3. 使用场景

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 默认方式:命令直接输出到控制台
    subprocess.run(["ls", "-l"])

    # 捕获输出方式:将输出存储在变量中
    result = subprocess.run(
    ["ls", "-l"],
    capture_output=True,
    text=True
    )
    print(result.stdout)
  4. 详细对比

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 不指定 PIPE 的情况
    result1 = subprocess.run(["echo", "hello"], check=True, text=True)
    # 输出会直接打印到控制台
    # result1.stdout 为 None

    # 指定 PIPE 的情况
    result2 = subprocess.run(
    ["echo", "hello"],
    check=True,
    text=True,
    stdout=subprocess.PIPE
    )
    # result2.stdout 会包含 "hello\n"
    # 输出不会直接打印到控制台
  5. 完整示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import subprocess

    # 不捕获输出
    subprocess.run(["ls", "-l"], check=True, text=True)

    # 捕获并处理输出
    result = subprocess.run(
    ["ls", "-l"],
    check=True,
    text=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE
    )
    print("标准输出:", result.stdout)
    print("错误输出:", result.stderr)

关键区别:

  • 默认方式:输出直接到控制台
  • 使用 PIPE:
    1. 输出被捕获到内存
    2. 不直接显示在控制台
    3. 可以通过 result.stdout/stderr 获取输出
    4. 适合需要处理命令输出的场景

通常建议:

  • 需要捕获或处理输出时,使用 PIPE
  • 只是执行命令,不关心输出时,使用默认方式

subprocess.run and subprocess.Popen

subprocess 的作用

  • 允许 Python 程序调用和控制外部系统命令或其他程序
  • 创建的是子进程,不是并行的 Python 进程
  • 主要用于系统交互,执行外部命令

subprocess.run()subprocess.Popen() 都是用于在 Python 中执行外部命令的方法,但它们有一些关键区别:

  1. 抽象层次
    subprocess.run() 是对 subprocess.Popen() 的高级封装。我们可以通过查看 Python 的源码来验证这一点。让我们看看 run() 函数的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs):
    if input is not None:
    if kwargs.get('stdin') is not None:
    raise ValueError('stdin and input arguments may not both be used.')
    kwargs['stdin'] = PIPE

    if capture_output:
    if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
    raise ValueError('stdout and stderr arguments may not be used '
    'with capture_output.')
    kwargs['stdout'] = PIPE
    kwargs['stderr'] = PIPE

    with Popen(*popenargs, **kwargs) as process:
    try:
    stdout, stderr = process.communicate(input, timeout=timeout)
    except TimeoutExpired as exc:
    process.kill()
    if _mswindows:
    exc.stdout, exc.stderr = process.communicate()
    else:
    process.wait()
    raise
    except: # Including KeyboardInterrupt, communicate handled that.
    process.kill()
    # We don't call process.wait() as .__exit__ does that for us.
    raise
    retcode = process.poll()
    if check and retcode:
    raise CalledProcessError(retcode, process.args,
    output=stdout, stderr=stderr)
    return CompletedProcess(process.args, retcode, stdout, stderr)
  2. 主要区别

    • subprocess.Popen()

      • 创建进程后立即返回
      • 需要手动管理进程(等待、读取输出、关闭)
      • 提供更细粒度的进程控制
      • 适合需要更复杂进程管理的场景
    • subprocess.run()

      • 创建进程并等待其完成
      • 自动处理进程等待、输出捕获
      • 返回一个 CompletedProcess 对象
      • 更简单、更直接的使用方式
      • 适合简单的命令执行
  3. 使用示例

    Popen() 方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import subprocess

    # 手动管理进程
    process = subprocess.Popen(['ls', '-l'],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True)
    stdout, stderr = process.communicate()
    print(stdout)

    run() 方式:

    1
    2
    3
    4
    5
    6
    7
    import subprocess

    # 简单直接的方式
    result = subprocess.run(['ls', '-l'],
    capture_output=True,
    text=True)
    print(result.stdout)
  4. 实现原理

    从源码可以看出,run() 实际上是:

    • 创建一个 Popen 对象
    • 使用 communicate() 方法等待进程完成
    • 处理超时
    • 检查返回码
    • 返回一个包含执行结果的 CompletedProcess 对象
  5. 性能和使用建议

    • 对于简单的命令执行,使用 run()
    • 对于需要实时交互、复杂进程控制的场景,使用 Popen()

总结:subprocess.run() 是对 subprocess.Popen() 的高级封装,提供了更简单、更直接的命令执行方式,同时在底层仍然使用 Popen 来创建和管理进程。

0%