ExampleDifficulty: Intermediate1 min

Facade in the Wild: Python's subprocess.run

One call over Popen, pipes, waiting and return codes.

Python's subprocess.run is a facade over the module's process machinery. Running a program and reading its output with the lower-level Popen class means creating the process with pipes, reading from them while waiting for it to end, and then reading the return code. run does all of that in one call and returns the results in one object. This card shows the two side by side.

py
import subprocess
import sys

command = [sys.executable, "-c", "print('converted 3 files')"]

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
stdout, stderr = process.communicate()
print("Popen:", stdout.strip(), "| return code", process.returncode)

result = subprocess.run(command, capture_output=True, text=True, check=True)
print("run:  ", result.stdout.strip(), "| return code", result.returncode)
OutputRan with python:3.12-slim (Python 3.12.13)
Popen: converted 3 files | return code 0
run:   converted 3 files | return code 0
The same program run with `Popen` and its pipes, then with one call to `run`.

Both produce the same output and return code. The Popen version names the pipes, calls communicate to read them and wait, and reads returncode afterwards. The run version asks for captured text output and an exception on failure, and gets back a CompletedProcess with stdout and returncode on it. In CPython, run is written with Popen and communicate inside it, which is the shape of a facade: a simple method over the classes that do the work.

Popen is still public, and the documentation recommends it for what run does not cover, such as reading output while a long-running process is still going. That is the second half of the pattern: the common case gets one call, and the subsystem stays open for the case that needs it.