Wurlitzer
Wurlitzer is a Python library that captures C-level `stdout` and `stderr` streams, making it possible to integrate output from underlying C code into Python's I/O system. This is particularly useful in environments like Jupyter notebooks or when calling native libraries that print directly to the console. As of April 2026, the current stable version is 3.1.1, and it maintains a steady release cadence, with recent updates focusing on stability and feature enhancements.
Warnings
- gotcha When using `sys_pipes` in complex environments (e.g., deeply nested context managers or with prior redirections of `sys.stdout`/`sys.stderr`), older versions of `wurlitzer` (prior to 3.1.0) on macOS could sometimes hang. This was due to specific interactions with file descriptor handling.
- gotcha `wurlitzer` operates by redirecting C-level file descriptors. While it uses a background thread to forward output, long-running C code that extensively holds the Python GIL (Global Interpreter Lock) might still cause perceived blocking or delays in output processing, especially if not configured to write to a file-backed pipe.
- deprecated While `wurlitzer` 3.1.1 officially supports Python >=3.5, there has been a GitHub Pull Request to drop support for Python versions older than 3.8. Future major or minor releases may officially drop support for Python 3.5, 3.6, and 3.7.
Install
-
pip install wurlitzer
Imports
- pipes
from wurlitzer import pipes
- sys_pipes
from wurlitzer import sys_pipes
- Wurlitzer
from wurlitzer import Wurlitzer
- STDOUT
from wurlitzer import STDOUT
- PIPE
from wurlitzer import PIPE
Quickstart
import ctypes
import io
from wurlitzer import pipes, STDOUT
# Simulate a C function that prints to stdout/stderr
libc = ctypes.CDLL(None) # Load standard C library
def c_printf(msg):
libc.printf(b'%s\n', msg.encode('utf8'))
def c_fprintf_stderr(msg):
# Find stderr pointer, differs slightly by OS
try:
c_stderr_p = ctypes.c_void_p.in_dll(libc, 'stderr')
except ValueError:
c_stderr_p = ctypes.c_void_p.in_dll(libc, '__stderrp')
libc.fprintf(c_stderr_p, b'%s\n', msg.encode('utf8'))
# Example 1: Capture stdout and stderr separately
out_buf = io.StringIO()
err_buf = io.StringIO()
with pipes(stdout=out_buf, stderr=err_buf):
c_printf('Hello from C stdout!')
c_fprintf_stderr('Hello from C stderr!')
print(f"Captured STDOUT: '{out_buf.getvalue().strip()}'")
print(f"Captured STDERR: '{err_buf.getvalue().strip()}'")
# Example 2: Forward C-level stdout/stderr to Python's sys.stdout/stderr
# which might already be redirected (e.g., in a Jupyter notebook)
from wurlitzer import sys_pipes
print("\n--- Using sys_pipes (output below is from Python's streams) ---")
with sys_pipes():
c_printf('C stdout via sys_pipes!')
c_fprintf_stderr('C stderr via sys_pipes!')
print("--- End sys_pipes example ---")