What Is stdout in Python? A Beginner's Tutorial

Final Exam & Certification

Complete this tutorial and pass the 10-question final exam to earn a downloadable certificate of completion.

skip to exam

Every time you call print() in Python, your text goes somewhere specific: standard output, or stdout. Understanding what stdout is, how Python connects to it, and how to control it gives you real insight into how programs communicate with the outside world.

Stdout is one of three standard streams that every Unix-like process inherits when it starts. The other two are stdin (standard input) and stderr (standard error). Python exposes all three through the sys module. When you type print("hello"), Python is calling sys.stdout.write("hello\n") under the hood. Once you understand that connection, redirecting output, suppressing it, or capturing it in a string all become straightforward operations.

What Is stdout?

stdout is short for standard output. It is a communication channel that a running process uses to send normal text output to whatever is listening. In a typical terminal session, that listener is your terminal emulator, so text written to stdout appears on screen.

The idea comes from Unix. When a process starts, the operating system hands it three open file descriptors:

Descriptor number
0
Default source
Keyboard input
Python object
sys.stdin
Descriptor number
1
Default destination
Terminal screen
Python object
sys.stdout
Descriptor number
2
Default destination
Terminal screen (error channel)
Python object
sys.stderr

Python wraps file descriptor 1 as sys.stdout, a text-mode file-like object. Any code that writes to sys.stdout ultimately writes to that file descriptor, which the operating system routes to your terminal.

One fact that rarely surfaces in beginner tutorials: you do not have to send stdout anywhere near a terminal. Because sys.stdout is just a file-like object, you can swap it for an io.StringIO instance to capture all printed output into a string in memory — without touching the filesystem at all. This is the standard technique for testing functions that call print().

python
import sys
import io

# Capture print() output into a string instead of the terminal
buffer = io.StringIO()
sys.stdout = buffer

print("line one")
print("line two")

sys.stdout = sys.__stdout__          # restore terminal

captured = buffer.getvalue()         # "line one\nline two\n"
print("Captured:", repr(captured))   # now printed to the real terminal

# The same pattern using contextlib for cleaner code
from contextlib import redirect_stdout

output = io.StringIO()
with redirect_stdout(output):
    print("captured cleanly")

print(output.getvalue())             # "captured cleanly\n"
Note

sys.stdout is not the same as file descriptor 1 at the OS level, but it wraps it. You can replace sys.stdout with any object that has a write() method, and Python's print() will use it seamlessly.

Pro Tip

Run import sys; print(type(sys.stdout)) in any Python environment. You will see <class '_io.TextIOWrapper'> — the same class Python uses for text files. stdout is just a file object pointing at your terminal.

python
import sys

# print() and sys.stdout.write() produce the same output
print("Hello from print()")
sys.stdout.write("Hello from sys.stdout.write()\n")

# Check that sys.stdout is a file-like object
print(type(sys.stdout))
# Output: <class '_io.TextIOWrapper'>
"Everything is a file" — Unix design philosophy, widely attributed to Ken Thompson and Dennis Ritchie

That principle is why stdout, stderr, and stdin are modelled as file objects in Python rather than as special-purpose APIs. You already know how to read and write files — and that is exactly what you are doing when you call sys.stdout.write(). The Unix designers made all I/O look the same so that programs could be composed freely, and Python inherited that decision directly.

One detail most tutorials skip: sys.stdout in CPython is not a raw binary stream. It is a TextIOWrapper sitting on top of a BufferedWriter, which itself sits on top of a FileIO. That three-layer stack is why you can call sys.stdout.buffer.write(b"raw bytes\n") when you need to bypass text encoding entirely — useful when writing binary data to a pipe or working with non-UTF-8 terminals.

code builder click a token to place it

Build the Python statement that writes "ready" to stdout using sys.stdout.write():

your code will appear here...
"ready\n" sys.stdin .write sys.stdout ) .read (
Why: The correct call is sys.stdout.write("ready\n"). sys.stdout is the object, .write is the method, and the string argument must include \n because write() does not add a newline automatically. sys.stdin is for reading input, not writing output, and .read is a reading method.

stdout vs stderr vs stdin

The three standard streams exist for different purposes, even though stderr and stdout both default to showing text in your terminal. Knowing the difference matters as soon as you start redirecting output or writing programs meant to be composed with other tools.

stdout carries the normal output of a program — the data it is designed to produce. When you pipe one program into another (python script.py | grep error), the shell connects the stdout of the first process to the stdin of the second.

stderr carries error messages and diagnostics. It is intentionally separate so that error messages do not contaminate the useful output when you redirect or pipe stdout. In Python, you write to stderr with sys.stderr.write() or by passing file=sys.stderr to print().

stdin carries input. It is what input() reads from. When you pipe data into a Python script (echo "hello" | python script.py), that data arrives on stdin.

One thing most tutorials do not explain clearly: when you pipe stdout to a file with shell redirection (python script.py > output.txt), only stdout goes to the file. Anything written to stderr still appears in your terminal. That split is intentional — it lets you log errors while still capturing clean output for further processing.

python
import sys

# This line goes to stdout — captured by > or |
print("Result: 42")

# This line goes to stderr — stays in the terminal even when stdout is redirected
print("Warning: no input file specified", file=sys.stderr)

# Shell demo (run in your terminal):
# python script.py > results.txt
# results.txt contains only "Result: 42"
# "Warning: no input file specified" still prints to the screen
Pro Tip

To redirect both stdout and stderr to the same file in bash, use python script.py > output.txt 2>&1. The 2>&1 part means "send stderr (fd 2) to wherever stdout (fd 1) is currently going." This is a Unix shell feature, not Python — but knowing it makes your Python scripts far easier to debug in production environments.

Python object
sys.stdout
Typical use
Normal program output — results, data, messages meant for the user or downstream processes
Python object
sys.stderr
Typical use
Error messages, warnings, and diagnostic output that should not mix with normal output when piping
Python object
sys.stdin
Typical use
Receiving input from the keyboard or from another program's piped output
spot the bug click the line that contains the bug

This script is supposed to write a message to stderr, not stdout. One line is wrong. Click the line you think is the bug, then hit check.

1 import sys
2
3 sys.stdout.write("Error: file not found\n")
4
5 # The message should go to stderr, not stdout
6 # stderr keeps error messages separate from normal output
The fix: Change sys.stdout.write(...) to sys.stderr.write(...). Error messages should go to stderr so they stay separate from normal program output. When you pipe stdout to another process, stderr still reaches your terminal, which is exactly what you want for diagnostics.

print() and sys.stdout Under the Hood

The print() function is essentially a convenience wrapper around sys.stdout.write(). When you call print("hello"), Python converts the argument to a string using str(), calls sys.stdout.write("hello"), and then calls sys.stdout.write("\n") to add the newline.

You can verify this by reading the print() signature: print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False). The file parameter defaults to sys.stdout, but you can pass any writable file-like object there. That is how you redirect print output to a file with a single argument.

There is a subtlety with buffering that catches beginners off guard. By default, stdout is line-buffered when connected to a terminal and fully buffered when connected to a pipe or file. That means if you redirect a Python script’s output to a file and the script crashes, the last few lines may never reach the file — they are still sitting in the buffer. The fix is either sys.stdout.flush() after critical writes, or launching Python with python -u (unbuffered mode), or passing flush=True to print().

Buffering Trap

When stdout is piped to a file or another process, it switches to fully-buffered mode with a 8 KB buffer. Output you expect to see immediately may be delayed until the buffer fills or the process exits. Use print(..., flush=True) or sys.stdout.flush() after time-sensitive writes, particularly in long-running scripts or real-time logging.

Another detail that rarely appears in introductory material: sys.stdout has an encoding attribute. On most modern systems it is utf-8, but on Windows it can be cp1252 or another legacy code page. If your script prints characters that fall outside the terminal’s encoding, Python raises a UnicodeEncodeError. The fix is either sys.stdout.reconfigure(encoding='utf-8') (Python 3.7+) or setting the environment variable PYTHONIOENCODING=utf-8 before launching the interpreter.

python
import sys

# Check the encoding of stdout
print(sys.stdout.encoding)        # e.g. utf-8

# Access the raw binary layer beneath the text wrapper
sys.stdout.buffer.write(b"raw bytes\n")

# Force a flush so piped output is not held in the buffer
print("status update", flush=True)

# Reconfigure encoding at runtime (Python 3.7+)
sys.stdout.reconfigure(encoding="utf-8", errors="replace")

# Check buffering mode
# True = line-buffered (connected to terminal), False = fully buffered (pipe/file)
print(sys.stdout.line_buffering)  # True when connected to a terminal
In Python, objects are the fundamental unit of data — every value, function, and class is an object with an identity, a type, and a value. — Python Data Model, docs.python.org

That includes sys.stdout. Because it is an ordinary object, you can subclass io.TextIOBase to create a custom stdout replacement — one that timestamps every line, filters profanity, or fans output to multiple destinations at once. This pattern is called a stream multiplexer and it is genuinely useful in logging pipelines.

python
import sys
import io
from datetime import datetime

class TimestampedWriter(io.TextIOBase):
    """Wraps stdout and prepends a timestamp to every line."""

    def __init__(self, stream):
        self._stream = stream

    def write(self, text):
        if text and text != "\n":
            ts = datetime.now().strftime("%H:%M:%S")
            text = f"[{ts}] {text}"
        return self._stream.write(text)

    def flush(self):
        self._stream.flush()

# Install — all print() calls now get timestamps
sys.stdout = TimestampedWriter(sys.__stdout__)
print("Starting process...")   # [14:03:21] Starting process...
print("Done.")                 # [14:03:22] Done.

# Restore when finished
sys.stdout = sys.__stdout__

Solutions That Go Beyond the Basics

Most tutorials stop at sys.stdout = some_file. These three patterns cover scenarios that come up in real codebases but rarely appear in introductory material.

OS-level fd redirection with os.dup2(). Replacing sys.stdout only redirects output from Python code. If your process loads a C extension, calls a subprocess, or uses a library that writes directly to file descriptor 1 at the OS level, Python’s sys.stdout swap has no effect on that output. The solution is to redirect at the file descriptor level using os.dup2(). This duplicates one file descriptor onto another, so every write to fd 1 — from any language, any thread, any extension module — goes to your chosen destination.

python
import os
import sys

# Redirect stdout at the OS file descriptor level.
# This captures writes from C extensions and subprocesses too —
# not just Python's sys.stdout.

def redirect_fd1_to_file(path):
    """Return (saved_fd, new_file) so the caller can restore later."""
    saved_fd = os.dup(1)                      # save a copy of fd 1
    new_file = open(path, "w")
    os.dup2(new_file.fileno(), 1)             # point fd 1 at the new file
    sys.stdout = new_file                     # keep Python in sync
    return saved_fd, new_file

def restore_fd1(saved_fd, new_file):
    sys.stdout = sys.__stdout__
    os.dup2(saved_fd, 1)                      # restore original fd 1
    os.close(saved_fd)
    new_file.close()

saved, f = redirect_fd1_to_file("/tmp/all_output.txt")
print("This comes from Python print()")
os.write(1, b"This comes from a raw fd write\n")   # also captured
restore_fd1(saved, f)

Capturing subprocess output with subprocess.PIPE. A common mistake is redirecting sys.stdout when the goal is to capture output from a child process. Those are completely different operations. sys.stdout is your process’s own output stream. Output from a subprocess spawned with subprocess.run() or subprocess.Popen() flows through a separate pipe. Use capture_output=True (Python 3.7+) or stdout=subprocess.PIPE to capture it correctly.

python
import subprocess

# WRONG approach: redirecting sys.stdout does NOT capture child process output
# sys.stdout = open("out.txt", "w")       # has no effect on the subprocess below

# CORRECT: use capture_output=True (Python 3.7+)
result = subprocess.run(
    ["python3", "-c", "print('hello from child')"],
    capture_output=True,
    text=True                              # decode stdout/stderr as strings
)
print(result.stdout)                       # "hello from child\n"
print(result.returncode)                   # 0

# For streaming output from a long-running child process:
with subprocess.Popen(
    ["python3", "-u", "-c", "import time; [print(i) or time.sleep(0.1) for i in range(5)]"],
    stdout=subprocess.PIPE,
    text=True
) as proc:
    for line in proc.stdout:
        print("received:", line, end="")

The logging module as the production alternative to raw stderr writes. Writing print("error", file=sys.stderr) is fine in scripts, but in any code that will run in production, a library, or a test suite, the logging module is the right tool. It routes messages to stderr by default, adds severity levels, timestamps, caller context, and lets calling code configure or silence output without modifying your source. The pattern is simple: replace print(..., file=sys.stderr) with a logger call.

python
import logging
import sys

# Basic setup: WARNING and above go to stderr, formatted with level + message
logging.basicConfig(
    stream=sys.stderr,
    level=logging.DEBUG,
    format="%(levelname)s: %(message)s"
)

logger = logging.getLogger(__name__)

# These replace direct sys.stderr.write() calls
logger.debug("diagnostic detail — visible only at DEBUG level")
logger.info("normal operational message")
logger.warning("something unexpected but non-fatal")
logger.error("something failed")
logger.critical("process cannot continue")

# Callers can silence your module's output without touching your code:
# logging.getLogger("your_module").setLevel(logging.CRITICAL)

# stdout still carries the program's actual output
print("result: 42")
How Python stdout works — print() calls sys.stdout.write(), which writes to OS file descriptor 1, which the terminal displays.

How to Use stdout in Python

There are three common ways to work with stdout: calling print(), writing directly via sys.stdout.write(), and redirecting stdout to a file.

  1. Import sys to access stdout directly

    Add import sys at the top of your script. The sys module exposes sys.stdout, sys.stderr, and sys.stdin as file-like objects you can read, write, and reassign.

  2. Write to stdout using sys.stdout.write()

    Call sys.stdout.write(text) where text is a string. The method does not add a newline automatically, so append "\n" when you need one. It returns the number of characters written.

  3. Redirect stdout to a file

    Assign an open file object to sys.stdout: sys.stdout = open("output.txt", "w"). All print() calls after that line write to the file. When finished, restore the terminal with sys.stdout = sys.__stdout__ and close the file.

python
import sys

# 1. Direct write — no automatic newline
sys.stdout.write("Line one\n")
sys.stdout.write("Line two\n")

# 2. print() using the file parameter to target stderr
print("This goes to stderr", file=sys.stderr)

# 3. Redirect stdout to a file
original_stdout = sys.stdout
with open("output.txt", "w") as f:
    sys.stdout = f
    print("This line goes into output.txt")
sys.stdout = original_stdout  # restore the terminal
print("Back to the terminal")

Python Learning Summary Points

  1. stdout is OS file descriptor 1. Python wraps it as sys.stdout — a TextIOWrapper on top of a BufferedWriter on top of a FileIO. That three-layer stack is why you can access raw bytes via sys.stdout.buffer.
  2. print() calls sys.stdout.write() internally. The file= parameter lets you target any writable stream — including sys.stderr, an open file, or an io.StringIO buffer.
  3. sys.stdout.write() requires an explicit "\n" for newlines and returns the number of characters written. It does not add any newline automatically.
  4. Buffering mode changes with the destination. stdout is line-buffered when connected to a terminal and fully buffered (8 KB) when piped or redirected. Use flush=True or python -u to force unbuffered output.
  5. stdout encoding is not always UTF-8 — particularly on Windows. Check sys.stdout.encoding and use sys.stdout.reconfigure(encoding="utf-8") if you need consistent Unicode output.
  6. You can capture all print() output into memory by replacing sys.stdout with an io.StringIO instance — or use contextlib.redirect_stdout for a cleaner, context-manager pattern. Both are standard techniques for testing output-producing functions.
  7. stderr and stdout are intentionally separate. When you redirect stdout to a file, stderr still reaches the terminal. To merge both into one file, use the shell syntax 2>&1.
  8. Replacing sys.stdout only redirects Python-level output. If a C extension or library writes directly to OS file descriptor 1, use os.dup2() to redirect at the file descriptor level — the only technique that captures output from every layer of the process.
  9. Redirecting sys.stdout does not capture output from child processes. Use subprocess.run(..., capture_output=True) or stdout=subprocess.PIPE to capture what a subprocess prints — these are entirely different I/O paths.
  10. For production code and libraries, prefer the logging module over print(..., file=sys.stderr). It writes to stderr by default, adds severity levels and caller context, and lets consumers configure or silence output without touching your code.

Once you understand that stdout is just a file object, every output operation in Python becomes a variation on the same theme: call a write method, target the right stream, mind the buffer, and check the encoding. That mental model scales from simple scripts all the way to logging pipelines, testing frameworks, and long-running services — and the three deeper patterns above cover the scenarios where the basic model alone is not enough.

check your understanding question 1 of N

Frequently Asked Questions

stdout stands for standard output. It is the default stream where a Python program sends its normal text output. In Python, it is available as sys.stdout, a file-like object that wraps OS file descriptor 1. When connected to a terminal, anything written to stdout appears on screen.

The print() function writes to sys.stdout by default. It converts its arguments to strings, joins them with the sep string (default: a space), writes the result, and then writes the end string (default: a newline). You can target a different stream using the file= parameter.

stdout carries the normal, intended output of a program. stderr carries error messages and diagnostics. They are separate streams so that errors do not contaminate the useful output when you pipe or redirect stdout. Both default to printing to the terminal, but they can be redirected independently.

Assign an open file object to sys.stdout before your output calls: sys.stdout = open("output.txt", "w"). All subsequent print() calls will write to that file. Store the original terminal reference first with original = sys.stdout, then restore it afterward with sys.stdout = original (or use sys.__stdout__).

No. Unlike print(), sys.stdout.write() writes exactly the string you pass — nothing more. If you need a newline at the end, you must include "\n" in the string yourself. This gives you precise control over output formatting.

Replace sys.stdout with an io.StringIO instance before the code you want to capture, then restore sys.__stdout__ afterward and call .getvalue() on the buffer. The cleaner approach is contextlib.redirect_stdout: wrap the code in a with redirect_stdout(buffer): block and the restoration happens automatically. Both techniques are standard in test suites when you need to assert on what a function prints.

When stdout is connected to a pipe or file rather than a terminal, Python switches from line-buffered to fully-buffered mode with an 8 KB buffer. Output does not reach the file until the buffer fills or the process exits. If your script crashes or is killed before that, the last chunk of output may be lost. The fixes are: pass flush=True to individual print() calls, call sys.stdout.flush() after critical writes, or launch Python with the -u flag to disable buffering entirely.

sys.stdout.buffer is the raw binary stream that sys.stdout (the TextIOWrapper) wraps. Writing to it bypasses text encoding entirely, letting you send raw bytes directly to the OS file descriptor. This is useful when you need to pipe binary data — images, compressed output, protocol buffers — through stdout without the text layer corrupting the bytes with encoding transformations. Call sys.stdout.buffer.write(b"...") and follow with sys.stdout.flush() to ensure the data is sent immediately.

Certificate of Completion
Final Exam
Pass mark: 80% · Score 80% or higher to receive your certificate

Enter your name as you want it to appear on your certificate, then start the exam. Your name is used only to generate your certificate and is never transmitted or stored anywhere.

Question 1 of 10