Reading and Writing Files

CSV Basics


Learning Objectives

  • You can explain what CSV is and why it is used.
  • You can read a CSV file with csv.reader and handle the header row.
  • You can write a CSV file with csv.writer, including a header.
  • You can convert string fields to numbers and remember that all CSV values arrive as text.
  • You can round-trip a CSV file and use the result as a test of your file code.

The last two chapters covered the mechanics of reading and writing text files, with .split() and .join() as the tools for breaking a line into fields and putting one together. They work fine for clean data, but they often quietly break on real data. This chapter introduces the csv module, which is Python’s built-in answer to that problem.

Comma-Separated Values

CSV stands for comma-separated values. A CSV file is plain text — you can open it in any editor — with one record per line and the fields on each line separated by commas. A study log might look like:

subject,minutes,date
Math,45,2026-05-10
Physics,90,2026-05-10
History,30,2026-05-11

The first line is usually a header — the names of the columns. The lines after it are the data, one row per line. CSV is everywhere: spreadsheets export to it, databases dump to it, scientific tools accept it. If you can read and write CSV reliably, you can interoperate with most of the data tooling in the world.

Why Not Just .split(",")?

The reading chapter showed you how to parse a line like "Math,45" with line.strip().split(","). For data that looks like the example above, that approach works fine. Why, then, is there a whole module for CSV?

The reason is that real CSV files contain fields that themselves can contain commas. Consider a study log where one of the subjects has a comma in its name:

subject,minutes,date
"History, ancient",60,2026-05-12
Math,45,2026-05-12

The first data row has three fields, not four. The quotation marks tell the reader that everything between them is one field, even though there is a comma inside. This convention — quote any field that contains the separator — makes CSV robust enough for the messy data that real files contain.

Quoted commas

0 / 5 points

Why should CSV code use csv.reader instead of .split(",") for real files?

Reading a CSV With csv.reader

The csv module is part of Python’s standard library, so you do not have to install anything — just import it:

import csv
with open("sessions.csv", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
print(row)

For the file shown earlier, this prints:

['subject', 'minutes', 'date']
['Math', '45', '2026-05-10']
['Physics', '90', '2026-05-10']
['History', '30', '2026-05-11']

Each row arrives as a list of strings and the rows include the header. Most of the time you do not want to treat the header as data, so you handle it separately:

import csv
with open("sessions.csv", encoding="utf-8") as f:
reader = csv.reader(f)
header = next(reader)
for row in reader:
subject = row[0]
minutes = int(row[1])
date = row[2]
print(f"{subject}: {minutes} minutes on {date}")

next(reader) pulls the very next row — the header — and stores it in a variable, leaving the data rows for the for loop. This is a common shape for CSV-reading code: read the header into its own variable, then loop over the rest.

CSV Values Are Always Strings

Every value that comes out of a CSV file is a string. There is no type information in a CSV file — 45 and "45" look identical on disk, both stored as the two characters 4 and 5. The csv.reader does not try to guess; it gives you strings, and converting them to other types is your job.

CSV reader value types

0 / 5 points

When csv.reader reads the row Math,45, what types do the fields have?

The same patterns from before — converting a string to a number — apply here, every time you want to do arithmetic on a CSV field:

subject = row[0] # already a string -- fine
minutes = int(row[1]) # need a number, so convert

If a row contains something that does not convert — int("forty-five") — the int(...) call raises ValueError, exactly as it did when the value came from input(...).

We’ll look into handling files with errors in the chapter Defensive Programming with Files and External Data.

Writing a CSV With csv.writer

The write side mirrors the read side. The same module provides csv.writer, and the parallel to the previous chapter is intentional: csv.writer does to .join() what csv.reader did to .split():

import csv
subjects = ["Math", "Physics", "History, ancient"]
minutes_list = [45, 90, 60]
with open("sessions.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["subject", "minutes"]) # header
for subject, minutes in zip(subjects, minutes_list):
writer.writerow([subject, minutes])

.writerow(...) takes a list and writes it as one CSV line, putting commas between the fields and quoting any field that needs it. The third row — "History, ancient" with its comma — gets written as "History, ancient",60, with the quotes added automatically.

One detail in the open(...) call is worth pointing out: newline="". This is specifically for opening a file for CSV writing, and it tells Python not to add its own newline handling on top of what the csv module already does.

Without it, some systems, especially Windows, can produce extra blank lines between rows.

If you have many rows to write at once, .writerows(...) (plural) writes a list of rows in one call:

with open("sessions.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["subject", "minutes"])
writer.writerows(list_of_rows)

Round-Tripping as a Test

To test whether your CSV-reading and writing code works, you can do a round-trip test: write some data to a file, read it back, and check that what came out matches what went in.

import csv
def write_sessions(path, subjects, minutes_list):
with open(path, "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["subject", "minutes"])
for subject, minutes in zip(subjects, minutes_list):
writer.writerow([subject, minutes])
def read_sessions(path):
subjects = []
minutes_list = []
with open(path, encoding="utf-8") as f:
reader = csv.reader(f)
next(reader) # skip header
for row in reader:
subjects.append(row[0])
minutes_list.append(int(row[1]))
return subjects, minutes_list
# Round-trip test:
write_sessions("test.csv", ["Math", "Physics"], [45, 90])
loaded_subjects, loaded_minutes = read_sessions("test.csv")
assert loaded_subjects == ["Math", "Physics"]
assert loaded_minutes == [45, 90]
print("Round-trip OK.")

If write-then-read produces the same data, the two functions are at least consistent with each other. The test does not prove that the file is in some particular format, but it does prove that the program’s understanding of the file is internally consistent — which catches most of the bugs that this kind of code has in practice.

A Few CSV Edge Cases to Remember

You will meet these again in the chapter Defensive Programming with Files and External Data, but it is worth naming them now so they are not surprises:

  • An empty file. No rows at all. next(reader) raises StopIteration — there is no header to read. Decide what your program does on an empty file (treat as no data? error out? something else?) and handle the case deliberately.
  • A header-only file. One row — the header — and nothing else. The header reads fine, the for loop simply runs zero times. Usually nothing special is needed; the program correctly produces zero sessions.
  • A row with the wrong number of fields. A line like Math,45 in a file whose other rows have three fields. The csv module gives you the row as a list of two strings, and your code crashes when it tries row[2]. Defensive code checks the length of the row before unpacking it.

These are the cases the csv module does not catch for you. The module handles syntactic correctness — quoting, separators, escaping. It does not validate that the rows make semantic sense. That part is yours.

Prompt a CSV writer function

0 / 10 points

Write a prompt that asks a large language model to create Python code only.

The generated program must define a function named write_book_log(path, books) with this exact behavior:

  • books is a list of rows where each row is [title, minutes],
  • open path in write mode,
  • use encoding="utf-8",
  • use newline="",
  • use csv.writer,
  • write the header row ["title", "minutes"],
  • write one row for each book,
  • preserve titles that contain commas, such as "History, short essays",
  • and include no input(...) calls.

The generated code will be tested by calling:

books = [["Python Basics", 45], ["History, short essays", 30]]
write_book_log("books.csv", books)

The resulting file must be readable with csv.reader as:

[
["title", "minutes"],
["Python Basics", "45"],
["History, short essays", "30"],
]

Your prompt should explicitly include the exact function name, the exact header, the requirement to use csv.writer, and the encoding="utf-8" and newline="" details.

Ask for Python code only, with no explanation.


DictReader

The csv module has a second reader called csv.DictReader that returns each row as a dictionary keyed by column name, so that you write row["subject"] instead of row[0]. This is often much clearer than positional access, but it requires knowledge of dictionaries, which we look into later in this course.

For now, csv.reader and csv.writer are enough. Just keep DictReader in mind as something that exists and that will become a natural upgrade later.

Check Your Understanding

  1. What does CSV stand for, and what does a CSV file look like?
  2. Give one concrete example of a row that .split(",") would parse incorrectly but csv.reader would parse correctly.
  3. When you read a CSV with csv.reader, what type is each row? What type are its fields?
  4. Why does open(..., "w", ..., newline="") exist? What goes wrong if you forget the newline=""?
  5. Explain in your own words what a round-trip test is, and what kind of bug it catches.

AI Study Aid

Create a chapter diagram

We're looking into to what extent AI-generated diagrams could help with studying.

Use this study aid to generate an AI-generated visual summary of the material you just studied. Each diagram style emphasizes the content in a different way, so you can choose the focus that feels most useful.

Using the diagram generator is voluntary and does not affect your course progress. We encourage you to try it out and see if it helps, but it's totally up to you! Your answers help us understand how to make better study aids in the future.

Diagrams tried: 0