Learn by reading through in order

File I/O — Safely Read and Write with with open()

Learn Python file I/O. with open() auto-close, read / readlines / readline differences, w / a modes, and the read-process-write pattern that saves to a separate file — hands-on.

Most of the data programs deal with lives on disk as files. Read a task list, append to a log, load a config — all are everyday operations.

In Python, with open() lets you do this in one safe syntactic unit. This article covers the basic form of with open(), the three reading methods (read / readlines / readline), writing (w) and appending (a), and handling paths with directories — by actually running code against files.

with open() Opens and Closes Files Safely

Opening a file is an operation that makes the OS hold a resource, and once opened, you must close it. Forgetting causes file-descriptor exhaustion, or write data getting stuck in buffers and never reaching disk.

Opening grabs an OS resource — closing is the contract
open()OS allocatesFDread / writeclose()releasesopen()OS allocatesFDread / writeforgot to close-> FD exhausted+ buffers unflushednormal
open() makes the OS allocate a file descriptor (FD). Until close(), the FD stays held and writes stay buffered, unflushed. Forgetting to close is a serious resource leak.

What is a file descriptor (FD)?

FD (file descriptor) is the integer ID the OS assigns to each currently-open file. Every open() hands out a new FD; close() returns it to the OS. The OS sets a per-process upper limit (Linux's default is around 1,024). Hit that limit and new open() calls start failing with OSError: Too many open files. The danger of forget-to-close is exactly this: holding a finite resource forever.

Write the form with open(path, mode) as f: and the file automatically closes the moment you leave the block, so the mistake is impossible to make. The f in as f is the variable that receives the file object; inside the with block, you read and write through f.

Paths use forward slashes / to separate directories. open("data/tasks.txt", "r") opens tasks.txt inside the data folder under the current directory. Even on Windows, Python convention uses / — internally, it's translated per OS.

Flow of with open()
with open(path, mode)bindas fcall f.read()or f.write()autocloseblock exits
Entering the with opens the file and binds it to as f. Leaving the block — normally or via exception — close() runs automatically.

Reading — read / readlines / readline

To read a file, use r (read) for the second argument of open(path, "r"). The returned file object has three reading methods for different needs.

- f.read() — reads the whole file as one string.

- f.readlines() — reads it as a list of lines (strings).

- f.readline() — reads one line. Repeated calls advance to the next line; an empty string "" signals end of file.

What the three reading methods return
f.read()f.readlines()f.readline()one strfull textlist oflinesone str(next call =next line)
read returns a string, readlines returns a list, readline returns one string per call. Pick by data size.
# Get the whole file as one string
with open("data/tasks.txt", "r") as f:
    content = f.read()
print(type(content))     # <class 'str'>

# Get a list of lines
with open("data/tasks.txt", "r") as f:
    lines = f.readlines()
print(type(lines))       # <class 'list'>

# Get one line at a time (next call -> next line)
with open("data/tasks.txt", "r") as f:
    first  = f.readline()   # "Write the report\n"
    second = f.readline()   # "Reply to emails\n"

From here on, the console runs in a browser-side virtual file system (VFS)

The console on the right is an in-browser virtual file system — files like data/tasks.txt are pre-staged for you. To run the same code on your local PC, create a data/ folder next to your Python file (e.g., main.py) and place tasks.txt inside it before running. The path syntax ("data/tasks.txt" with /) is identical to real Python.

Click the 📂 Files button at the top of the right console, and you'll see the two pre-staged files data/tasks.txt (4 tasks) and data/log.txt (3 history lines).

① Use with open("data/tasks.txt", "r") as f: to open the file in read mode.

② Read everything with f.read() and store it in content.

③ Print it with print(content).

(If your code runs correctly, the explanation will appear.)

Python Editor

Run code to see output

When the file is one record per line (CSV without a header, a newline-separated list, log lines), f.readlines() is convenient. It returns a list of strings, one per line, and each element keeps its trailing \n.

Printing it directly produces double-spacing, so the standard pattern is to apply .rstrip("\n") to drop the trailing newline.

Use data/log.txt (3 history lines). Read it as a list with readlines() and print each line numbered. This is the typical pattern for log views and ops report formatting.

① Open with with open("data/log.txt", "r") as f: and store f.readlines() in lines.

② Loop with enumerate(lines, start=1) to start numbering from 1.

③ Strip the trailing newline with .rstrip() and print as f"{n}: {body}".

Python Editor

Run code to see output

Big files? Read line by line — readline and walrus

f.read() and f.readlines() load the whole file into memory. That's fine for config-sized files, but a multi-GB server log can crash the program by running out of memory.

f.readline() loads only one line at a time, so memory usage stays at a single line's worth. When readline() reaches end of file, it returns the empty string "", which you use as the loop's exit condition.

readline() reads one line at a time
file(several GB)readline()-> line 1memory:line 1(continued)readline()-> line 2memory:line 2EOF reachedreadline()-> ""loopends
Read one line, process it, overwrite with the next — the whole file never lives in memory. Even a multi-GB file uses only "the longest line's worth" of memory. EOF returns "" to signal the loop to stop.
# Classic style: break on empty string
with open("data/log.txt", "r") as f:
    while True:
        line = f.readline()
        if not line:           # empty string = EOF
            break
        print(line.rstrip())

The walrus operator := writes the same in one fewer line

Python 3.8 introduced the walrus operator (:=), which lets you assign and check inside an expression. Writing while line := f.readline(): means "put readline()'s result into line, and keep looping while it's non-empty" — the whole loop in one line.

Now read data/tasks.txt with readline() + the walrus operator, one line at a time. The real-world use case is gigantic logs; we're using the small task file just to see the mechanics.

① Open with open("data/tasks.txt", "r") as f:.

② Loop with while line := f.readline(): so each line goes into line.

③ Strip the trailing newline with .rstrip() and print: print(line.rstrip()).

Python Editor

Run code to see output

Writing — w mode and a mode

Writing is just open() with a different second argument. Two modes carry the practical weight.

- "w" (write) — create new or overwrite. Existing contents are destroyed.

- "a" (append) — append to the end. Existing contents stay.

Two write methods exist too. f.write(s) writes the string s directly; f.writelines(lst) writes a list of strings in one go. If the path includes a folder, the file is created inside it (the folder must already exist).

w vs a
before:ABCopen("w")f.write("XYZ")after:XYZbefore:ABCopen("a")f.write("XYZ")after:ABCXYZoverwriteappend
w wipes existing content the moment you open it. a appends from the end. Picking the wrong one can blow away an important log — be deliberate.
# w mode: write strings one at a time
with open("data/done.txt", "w") as f:
    f.write("Write the report\n")
    f.write("Reply to emails\n")

# w mode: write a list at once with writelines
with open("data/done.txt", "w") as f:
    f.writelines(["Write the report\n", "Reply to emails\n", "Attend the meeting\n"])

# a mode: append to the end
with open("data/done.txt", "a") as f:
    f.write("Do the shopping\n")

Newlines \n are not added automatically

Unlike print(), neither f.write() nor f.writelines() adds newlines for you. Calling f.write("Hello") twice gives you HelloHello in the file. Insert explicit \n wherever you actually want a line break.

Create a new done.txt (a record of completed tasks) inside the data/ folder, write to it, and read it back. The data/ folder already exists, so use it directly in the path.

① Open with with open("data/done.txt", "w") as f:.

② After f.write("Done tasks:\n"), write f.write("Write the report\n") then f.write("Reply to emails\n") — three writes total.

③ Open again with a separate with open("data/done.txt", "r") as f: and print(f.read()) to display the whole content.

④ After running, click the header's 📂 Files button — you'll see data/done.txt listed in the panel.

Python Editor

Run code to see output

Reproduce the state of data/done.txt from the last exercise, then append a new item to the end. Experience the difference between w and a in a single script.

① Start with with open("data/done.txt", "w") as f: and f.write("Done tasks:\nWrite the report\n") to set the initial content (this resets the file every run).

② Then with with open("data/done.txt", "a") as f:, append f.write("Reply to emails\n").

③ Finally, open with "r" again and print(f.read()) to verify.

Python Editor

Run code to see output

Going further — Analyze a file and save the result to another file

One of the most common patterns in real work is read an input file, do some analysis, and write the result to a separate file. Just use open() twice — once with "r", once with "w". The cleanest style is to put input → process → output into three separate with blocks.

# Analyze data/log.txt and save the result to data/log_summary.txt

# (1) Input: read the log into a list of lines
with open("data/log.txt", "r") as src:
    lines = src.readlines()

# (2) Process: count lines, capture first/last entries
total   = len(lines)
first   = lines[0].rstrip()
last    = lines[-1].rstrip()
summary = f"count: {total}\nfirst: {first}\nlast: {last}\n"

# (3) Output: write to a different file
with open("data/log_summary.txt", "w") as dst:
    dst.write(summary)

print("Saved analysis to data/log_summary.txt")

data/products.txt holds 4 lines in the form "name,price". Read it, compute the total, and write the result to a separate file data/total.txt.

① Open with with open("data/products.txt", "r") as f: and get the list with f.readlines().

② For each line, drop the trailing newline with .rstrip() and split on "," to get name and price. The price is a string, so convert with int() before adding.

③ Open with open("data/total.txt", "w") as f: and write the result with f.write(f"total: {total}\n").

④ Verify by opening with "r" and print(f.read()).

Python Editor

Run code to see output

encoding="utf-8" is the standard for text encoding

On real Python, you typically write open(path, "r", encoding="utf-8") to specify the encoding explicitly. Trying to read a Shift-JIS file as UTF-8 raises UnicodeDecodeError, and the reverse mojibakes silently. The world standard is UTF-8 — write new files in UTF-8 by default. The browser environment hard-codes UTF-8 internally so omitting encoding= works here, but make it a habit when writing local scripts.

QUIZ

Knowledge Check

Answer each question one by one.

Q1What does the second argument "r" in with open("data/notes.txt", "r") as f: mean?

Q2When you call f.readline() repeatedly and the file ends, what value comes back?

Q3If data/done.txt already has content and you open it with with open("data/done.txt", "w") as f:, what happens to the existing content?