subprocess
Popen
call
Python
programming

What's the difference between subprocess Popen and call how can I use them?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python's subprocess module lets your program start and control other processes. The main difference between subprocess.Popen and subprocess.call is control: call is a simple blocking helper, while Popen gives you direct access to the running process.

What subprocess.call does

subprocess.call() runs a command, waits for it to finish, and returns the exit code. It is convenient when you only care whether the command succeeded.

python
1import subprocess
2
3code = subprocess.call(["python3", "-c", "print('hello from child')"])
4print(code)

If the child exits successfully, the return code is usually 0. If it fails, you get a non-zero integer.

This is straightforward, but it is also limited. You do not get much process control beyond waiting for completion.

What subprocess.Popen does

Popen creates a process object immediately and returns control to your Python code. You can decide when to wait, capture output, send input, terminate the process, or poll its status.

python
1import subprocess
2
3process = subprocess.Popen(
4    ["python3", "-c", "import time; time.sleep(1); print('done')"],
5    stdout=subprocess.PIPE,
6    stderr=subprocess.PIPE,
7    text=True,
8)
9
10stdout, stderr = process.communicate()
11print(process.returncode)
12print(stdout.strip())

That flexibility is why Popen is the foundation under the higher-level helpers.

Blocking versus non-blocking behavior

call() blocks by design. Your script stops at that line until the child process exits.

With Popen, your script can continue doing work before waiting.

python
1import subprocess
2import time
3
4process = subprocess.Popen(["python3", "-c", "import time; time.sleep(2)"])
5
6for index in range(3):
7    print(f"main loop step {index}")
8    time.sleep(0.5)
9
10process.wait()
11print("child finished")

This pattern is useful when several child processes should run concurrently or when the parent needs to manage a long-running tool.

Capturing output safely

If you need stdout or stderr, Popen gives you full control through pipes. You can then call communicate() to read output and wait for exit.

python
1import subprocess
2
3process = subprocess.Popen(
4    ["python3", "-c", "print('stdout line'); print('error line', file=__import__('sys').stderr)"],
5    stdout=subprocess.PIPE,
6    stderr=subprocess.PIPE,
7    text=True,
8)
9
10stdout, stderr = process.communicate()
11print("STDOUT:", stdout.strip())
12print("STDERR:", stderr.strip())

When you read from pipes manually, be careful not to deadlock the child by leaving buffers unread. communicate() is usually the safe default.

Timeouts and process lifecycle

Popen also helps when the child might hang or when you need to stop it explicitly.

python
1import subprocess
2
3process = subprocess.Popen(["python3", "-c", "import time; time.sleep(10)"])
4
5try:
6    process.wait(timeout=1)
7except subprocess.TimeoutExpired:
8    process.terminate()
9    process.wait()
10    print("child terminated after timeout")

That kind of lifecycle control is difficult to express with call() alone.

Modern Python note: prefer run() over call() in new code

Although call() still exists, modern code often uses subprocess.run() instead because it combines simplicity with better options for capturing output and raising exceptions.

python
1import subprocess
2
3result = subprocess.run(
4    ["python3", "-c", "print('hello')"],
5    capture_output=True,
6    text=True,
7    check=True,
8)
9
10print(result.stdout.strip())

A useful rule is:

  • use run() for simple, blocking commands
  • use Popen when you need ongoing control over the process lifecycle

Common Pitfalls

A common mistake is using Popen when run() or call() would be simpler. If you just need an exit code, a full process object is unnecessary complexity.

Another issue is forgetting that Popen is not finished when it is created. If you need the result, you must call wait(), communicate(), or otherwise monitor the process.

A third problem is passing shell=True without a real need. It changes quoting rules and can introduce command injection risks when user input is involved.

Summary

  • 'subprocess.call() runs a command, waits, and returns only the exit code.'
  • 'subprocess.Popen starts a process and gives you fine-grained control over stdin, stdout, stderr, and timing.'
  • Use Popen for asynchronous, interactive, or timeout-sensitive process management.
  • In new blocking code, subprocess.run() is usually a better convenience API than call().
  • Avoid shell=True unless you specifically need shell features and understand the quoting risks.

Course illustration
Course illustration

All Rights Reserved.