Files & context managers
Read and write text and binary files, file modes, pathlib basics, the with statement and custom context managers.
Variables vanish when your program ends. To keep data — logs, settings, reports, exports — you write it to files. Python makes reading and writing files simple, and the with statement makes doing it safely just as simple. Along the way you'll learn what a context manager is and how to write your own.
Writing a text file#
open(path, mode, encoding=...) returns a file object. Note:
write()does not add a newline — include\nyourself (or useprint(..., file=f)).- Always pass
encoding="utf-8"for text files. Without it Python uses the platform default, which differs between Linux and Windows and causes mojibake bugs with non-English text. - When the
withblock ends, the file is closed automatically and buffered data is flushed to disk.
Reading a text file#
Assume notes.txt from above exists. There are several ways to read it:
Iterating over the file is the idiomatic way to process large files: Python reads one line at a time, so even multi-gigabyte logs use almost no memory. f.read() and f.readlines() load everything at once — fine for small files only.
Each line from iteration keeps its trailing \n, which is why we call rstrip("\n") (or strip()).
File modes#
Binary files#
Images, PDFs, zip files and anything that isn't text must be opened in binary mode. You read and write bytes objects instead of strings:
For simple copies, shutil.copyfile("a", "b") does this for you.
Paths with pathlib#
Hard-coding "folder/file.txt" breaks on Windows, and string-gluing paths is error-prone. The modern approach is pathlib.Path:
Path.read_text() and Path.write_text() open, read/write and close in one call — perfect for small files. You'll tour more of pathlib in the standard library lesson.
Handling missing files#
Opening a file that doesn't exist raises FileNotFoundError. Handle it where it makes sense:
PermissionError and IsADirectoryError are the other common ones; all three inherit from OSError.
Context managers: what with really does#
A context manager is any object that defines setup and teardown around a block. with calls its __enter__ method at the start and guarantees its __exit__ runs at the end — whether the block finishes normally, returns early, or raises an exception. Without with, you'd need this:
Files aren't the only context managers. You'll meet locks (threading.Lock), database connections, temporary directories, and more. Some useful ones from the standard library:
Writing your own context manager#
The easiest way is the @contextmanager decorator from contextlib: write a generator function, put setup before yield and teardown after it (inside try/finally so it always runs):
You can also write a class with __enter__ and __exit__ methods — you'll see that style in the magic methods lesson.
Worked example: summarising a log file#
Common mistakes#
- Forgetting
encoding="utf-8"— works on your machine, breaks on someone else's. - Opening with
"w"when you meant"a"— the old content is gone instantly. - Not using
with— files may stay open, and written data may not be flushed. - Relative paths are relative to the current working directory, not to the script. Use
Path(__file__).parent / "data.txt"to locate files next to your script. - Reading huge files with
read()— iterate line by line instead.
What's next#
Files fail in many ways — missing, locked, malformed. Next you'll learn to handle failures properly with exceptions, and to define your own.
Check your understanding
Quick quiz
1.Why is
with open("data.txt") as f:preferred overf = open("data.txt")?2.What does opening a file with mode
"w"do if the file already exists?3.What is the most memory-efficient way to process a 10 GB log file line by line?
Finished reading?
Mark this lesson complete to track your progress.