Python’s print() function defaults to writing to the console, but it also accepts a file keyword argument that redirects output to any writable file object. This works with any object that has a .write() method — files, io.StringIO, and similar streams. The idiomatic approach pairs print() with a context-managed file handle using with open(…) as f:, which handles closing automatically.
Redirecting print() to a file is useful for logging during development, saving script output to disk without modifying program logic, and writing data export pipelines where you want human-readable output. Four common approaches exist, ranging from the recommended print(file=f) pattern to sys.stdout redirection and the logging module.
TLDR
- Use
print('text', file=f)withwith open(path, 'w') as f:— the standard approach - Pass
file=fexplicitly — avoidsys.stdoutredirection for normal file output - Use mode
'w'to overwrite or'a'to append.print()adds newlines automatically, unlikef.write() - For production logging, use the
loggingmodule withFileHandlerinstead ofprint()
The print(file=f) Approach
The print() function accepts a file keyword argument that accepts any file-like object. The recommended pattern uses a context manager:
with open("output.txt", "w") as f:
print("First line", file=f)
print("Second line", file=f)
print("Third line", file=f)
After running this, output.txt contains three lines. The context manager closes the file automatically when the with block exits, even if an exception occurs.
To append instead of overwriting, change the mode to 'a':
with open("output.txt", "a") as f:
print("Appended line", file=f)
f.write() vs print(file=f)
The f.write() method and print(file=f) behave differently:
# write() does NOT add a newline by default
with open("output.txt", "w") as f:
f.write("Line 1")
f.write("Line 2") # Appears right after Line 1 on the same line
# print() automatically adds a newline (unless end="")
with open("output.txt", "w") as f:
print("Line 1", file=f)
print("Line 2", file=f) # Each on its own line
Use f.write() when you need precise control over what gets written — no automatic newlines, no spaces. Use print(file=f) for convenient line-by-line output where the newline should be added automatically.
One common mistake is opening the file without a context manager:
# WRONG - file is never explicitly closed
print("Data", file=open("output.txt", "w"))
# RIGHT - context manager ensures close
with open("output.txt", "w") as f:
print("Data", file=f)
Redirecting sys.stdout
Python’s sys.stdout is a file-like object that print() writes to by default. You can replace it with a custom file object to capture all print() calls:
import sys
# Save original stdout
original_stdout = sys.stdout
# Redirect all print() calls to a file
with open("output.txt", "w") as f:
sys.stdout = f
print("This goes to the file")
print("So does this")
# Restore stdout
sys.stdout = original_stdout
print("This prints to the console")
This approach works but has significant drawbacks: it affects the entire process, makes debugging harder because console output disappears, and must be carefully restored or the program crashes. Reserve sys.stdout redirection for situations where you genuinely need to intercept all output — not for normal file writing.
Using the logging Module
The logging module is the right choice for production-grade file output. It supports multiple severity levels, log rotation, and structured output. The simplest file configuration uses basicConfig:
import logging
logging.basicConfig(
filename="app.log",
level=logging.DEBUG,
format="%(levelname)s: %(message)s"
)
logging.debug("Debug info")
logging.info("Process started")
logging.warning("Low memory")
For more control, use FileHandler directly:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.info("Application running")
The logging module is superior to print() for any output that survives beyond development — it supports rotation, multiple handlers, and configurable severity thresholds.
Common Pitfalls
- Forgetting to close the file. Opening a file without a context manager or explicit
.close()can leave data unflushed. Always usewith open(...) as f:. - Using
'w'instead of'a'. Write mode ('w') truncates the file before writing. Append mode ('a') preserves existing content and adds to the end. - Mixing
write()andprint(file=f)on the same handle. Both work, but they have different buffering behavior. Pick one approach per file and stick with it. - Printing to a closed file. If the context manager block exits before all prints complete, the file closes and subsequent writes raise
ValueError: I/O operation on closed file.
FAQ
Q: Does print(file=f) automatically flush the data?
By default, print() flushes its output when a newline is written (the end="\n" default). To flush immediately without a newline, use print(value, file=f, flush=True).
Q: Can print() write to a file opened in binary mode?
print() writes text to files opened in text mode (the default). For binary output, use f.write() on a file opened with "wb" — print() raises a TypeError on binary file handles.
Q: How is print(file=f) different from sys.stdout redirection?
print(file=f) explicitly targets a single file object and leaves sys.stdout unchanged. sys.stdout redirection affects every print() call in the entire process and requires manual restoration.
Q: Can I print to stdout and a file at the same time?
Yes — write to stdout explicitly while also writing to the file, or use tee-style logic where each print goes to both destinations. The logging module handles this with multiple handlers.
Q: Does the logging module overwrite or append to the log file?
By default, FileHandler opens the file in append mode. Pass mode="w" when creating the handler to overwrite instead: FileHandler("app.log", mode="w").
