How to Read and Write CSV Files in Python: A 2026 Guide

To read and write CSV files in Python you use one module: csv, which ships with the interpreter. Reading runs through csv.reader or csv.DictReader, writing runs through csv.writer or csv.DictWriter. No installation, no pandas. Everything below runs on Python 3.8 through 3.13 and takes about ten minutes to work through end to end.

One thing to know before you start: a CSV file is plain text, and the text has opinions about quoting, newlines and character encoding. Get those three right and the rest is short. Get them wrong and you get blank lines between rows, stray quotes, or a UnicodeDecodeError on line one.

Table of Contents

What You Need

You need four things, and three of them are already on your machine.

  • Python 3.8 or newer. Check with python3 --version. Every example here works unchanged on 3.8, 3.11, 3.12 and 3.13.
  • The standard-library csv module. It is imported with import csv and needs no install step.
  • A text editor or a REPL. A plain editor is fine; so is running python3 in a terminal and typing the examples line by line.
  • A working directory. Paths in these examples are relative, so the script must run from the folder holding the file. Use pathlib.Path("data/people.csv") when the file lives somewhere else.

File paths are worth one more sentence. open("people.csv") looks in the current working directory, which is not always where your script sits. If you keep scripts in one folder and data in another, build the path once and reuse it.

You will also meet file modes constantly, so here is the short version.

ModeWhat it does to an existing fileTypical use
rReads it; fails if the file is missingOpening an export for analysis
wTruncates it, then writesRegenerating a report from scratch
aWrites at the end, keeps existing rowsLogging job or sensor output over time
xCreates it, errors if it already existsRefusing to clobber a file you care about

Every one of those modes is combined with two keyword arguments in this guide: newline="" and encoding="utf-8". Step 6 explains why both matter.

Step-by-Step: Read and Write CSV Files in Python

Step 1: Create a Sample CSV File to Work With

Every example below uses the same four-column file, people.csv, so the steps chain together. Write it from Python rather than typing it by hand, so the quoting is correct.

import csv

rows = [
    ["name", "id", "age", "department"],
    ["Ruiz, Ana", 1001, 34, "Support"],
    ["Bo Chen", 1002, 29, "Hardware"],
    ["Sofia Marek", 1003, 41, "Research"],
]

with open("people.csv", "w", newline="", encoding="utf-8") as handle:
    writer = csv.writer(handle)
    writer.writerows(rows)

The file now contains exactly this:

name,id,age,department
"Ruiz, Ana",1001,34,Support
Bo Chen,1002,29,Hardware
Sofia Marek,1003,41,Research

Look at the second line. Ana Ruiz has a comma in her name, so the csv module wrapped the value in quotes. That is QUOTE_MINIMAL at work: quote only the fields that need it. The id values are written unquoted here, so a spreadsheet will strip the leading zeros if you ever use zero-padded identifiers. When that matters, write them as strings and set quoting=csv.QUOTE_ALL.

Step 2: How to Read a CSV File Row by Row in Python

Read it with csv.DictReader and access values by column name instead of by position.

import csv

with open("people.csv", newline="", encoding="utf-8") as handle:
    reader = csv.DictReader(handle)

    print(reader.fieldnames)

    for row in reader:
        print(row["name"], "|", row["age"], "|", row["department"])

Output:

['name', 'id', 'age', 'department']
Ruiz, Ana | 34 | Support
Bo Chen | 29 | Hardware
Sofia Marek | 41 | Research

Three details do the work here. reader.fieldnames is the first row of the file, read once and reused for every record. The loop hands you one dict per row, so column order in the file can change without breaking your code. And the quoting is already undone: row["name"] is Ruiz, Ana, commas and all.

If you need the raw row order instead, csv.reader gives you each row as a list. It is faster, and every value is a string.

with open("people.csv", newline="", encoding="utf-8") as handle:
    for index, row in enumerate(csv.reader(handle)):
        print(index, row)

Start with DictReader anyway. Users on r/pythontips and r/learnpython describe the same switch: code that used line.split(",") broke the moment a value contained a comma, and the move to DictReader made it self-documenting.

Step 3: Read CSV Data Into a List of Dictionaries

Wrap the reader in list() when you want random access, filtering, sorting or passing the data to a function.

import csv

with open("people.csv", newline="", encoding="utf-8") as handle:
    people = list(csv.DictReader(handle))

print(len(people))
print(people[0]["name"], int(people[0]["age"]))
print([p["department"] for p in people if int(p["age"]) > 30])

Output:

3
Ruiz, Ana 34
['Support', 'Research']

Everything from a CSV arrives as text, so int() and float() conversions are on you. The sample set is tiny; hold the whole file in memory only while it comfortably fits. Step 6 and the FAQ cover the large-file case.

Step 4: Write Rows With csv.writer

Use csv.writer when your data is a list of lists, or a list of tuples.

import csv

rows = [
    ["name", "id", "age", "department"],
    ["Dara Okafor", 1004, 38, "Support"],
    ["Ibrahim Sy", 1005, 47, "Hardware"],
]

with open("new_people.csv", "w", newline="", encoding="utf-8") as handle:
    writer = csv.writer(handle)
    writer.writerow(rows[0])
    writer.writerows(rows[1:])

The result:

name,id,age,department
Dara Okafor,1004,38,Support
Ibrahim Sy,1005,47,Hardware

The only difference between writerow() and writerows() is how many rows they take: one iterable, or one iterable of iterables. Both return the number of values written by the underlying writer, which is rarely what you need. For anything with more than a couple of rows, call writerows() once and keep writerow() for the header.

Step 5: Write Dictionaries With csv.DictWriter

DictWriter is the practical choice when your rows are dictionaries, and it is the one beginners leave out. It has a rule the others do not: you must declare fieldnames first, because the file has no header until you tell it what the columns are.

import csv

fieldnames = ["name", "id", "age", "department"]

record = {
    "name": "Lena Fischer",
    "id": 1006,
    "age": 33,
    "department": "Research",
    "note": "contractor",   # extra key, not a column
}

with open("contractors.csv", "w", newline="", encoding="utf-8") as handle:
    writer = csv.DictWriter(handle, fieldnames=fieldnames,
                            extrasaction="ignore")
    writer.writeheader()
    writer.writerow(record)

Output file:

name,id,age,department
Lena Fischer,1006,33,Research

Without extrasaction="ignore" that last dictionary raises ValueError, because DictWriter refuses to write keys it was not given. The fieldnames list also fixes the column order, so reordering the file means reordering one list instead of every row.

Step 6: Handle Encoding, Newlines, and Special Characters

This is the step that separates scripts that work on your machine from scripts that work on a colleague’s. Two arguments do most of the work: encoding="utf-8" and newline="".

Why newline="". Python translates newlines on the way in and out of a text file. On Windows, a line you write arrives at the file as rn, then gets translated again on reading, which lands a second line break in your CSV. The csv module handles line endings itself, so it asks you to switch translation off. Omit it and every row is followed by a blank line, on Windows mostly, which is why the bug looks like a platform ghost.

# wrong on Windows: blank line after every record
with open("broken.csv", "w", encoding="utf-8") as handle:
    csv.writer(handle).writerows(rows)

# right
with open("correct.csv", "w", newline="", encoding="utf-8") as handle:
    csv.writer(handle).writerows(rows)

Same data, two different files on disk: the first has an empty line between records, the second does not. Multiple sources on discuss.python.org and Stack Overflow land on the same rule: always pass newline="", and always use with open() so the handle closes even if the loop raises.

Quoted commas and embedded line breaks. You do not handle these by hand. Write a value containing a comma or a newline and the module quotes it, so a round trip survives.

import csv

with open("notes.csv", "w", newline="", encoding="utf-8") as handle:
    writer = csv.writer(handle)
    writer.writerow(["name", "note"])
    writer.writerow(["Ana Ruiz", "shift lead,nday shift"])

with open("notes.csv", newline="", encoding="utf-8") as handle:
    for row in csv.DictReader(handle):
        print(repr(row["note"]))

Output:

'shift lead,nday shift'

Encoding. UTF-8 is the right default and it is what you should pass explicitly, since the default depends on your operating system. Two failures are worth recognising on sight.

  • UnicodeDecodeError: 'utf-8' codec can't decode byte usually means a legacy Windows export in cp1252 or latin-1. Read it with encoding="cp1252", or errors="replace" if you would rather lose the odd character than the run.
  • A leading  in the first column name means the file carries a byte order mark, which is what Excel writes by default. Open it with encoding="utf-8-sig" and the marker disappears.

If the delimiter is not a comma, pass it as delimiter=";" (common in European exports) or let the module guess with csv.Sniffer().sniff(sample). A file with a semicolon delimiter read as a comma produces a single giant row, which is the classic “my CSV is one column” bug.

Common CSV Mistakes in Python

Each row below is a symptom people actually search for, the cause behind it, and the fix.

SymptomCauseFix
Blank line between every written rowNewline translation on WindowsOpen with newline=""
UnicodeDecodeError on the first readFile is cp1252, or has an Excel BOMencoding="cp1252", or encoding="utf-8-sig" for the BOM
_csv.Error: new-line character seen in unquoted fieldA raw line break inside a value that was never quotedOpen with newline="" and strip line breaks from the value first
KeyError: 'department'Header name differs, or has stray spaces or a BOMPrint reader.fieldnames, strip whitespace, or use row.get("department")
Whole file reads as one long rowDelimiter is a semicolon or a tabdelimiter=";", or csv.Sniffer().sniff(sample)
Yesterday’s rows vanishedMode w truncates the file on every runUse a to append, and write the header only if the file is new
Every value is a string, sums failThe csv module never converts typesConvert explicitly, or write with quoting=csv.QUOTE_NONNUMERIC and read back with the same setting
_csv.Error: field larger than field limitOne cell exceeds the 128 KB defaultRaise it with csv.field_size_limit(10 * 1024 * 1024)
Quoted commas split into extra columnsHand-rolled line.split(",")Use the csv module; it is the quoting-aware parser

A few habits that prevent most of the above:

  • Always use with open(...). The block closes the file, flushes the buffer and releases the handle.
  • Pass newline="" and encoding="utf-8" on every read and write, even for a file you created yourself.
  • Append with mode a and guard the header: writeheader() only if the file was empty before you opened it.
  • Use extrasaction="ignore" when your dictionaries may carry extra keys.
  • Use strict=True in the reader while debugging a malformed file, so bad quoting raises instead of being silently repaired.

Frequently Asked Questions

What is the best way to read a CSV file in Python?

For most Python applications, use csv.DictReader because it preserves column names and lets you access values by name instead of fragile numeric positions. Open the file with encoding=’utf-8′ and newline=”, then loop over the reader so memory stays flat. Reach for csv.reader only when you genuinely need column order and speed, and for pandas only once filtering, grouping or analysis dominates the job.

How do I write a CSV file from a list of dictionaries in Python?

Use csv.DictWriter, define its fieldnames in the desired column order, call writeheader() once, and pass each dictionary to writerow(). Set extrasaction=’ignore’ only when your dictionaries may contain keys that are not columns. Open the file with mode=’w’, newline=” and encoding=’utf-8′ so the output stays clean on Windows and for non-ASCII names.

Should I use the csv module or pandas for CSV files?

Use the built-in csv module for scripts, small-to-medium files, and any job where adding a dependency is unwelcome. It needs no install and gives you exact control over quoting and delimiters. Pandas earns its place when you filter, group, join, plot, or handle a file that will not fit in memory, because a DataFrame carries types and column operations that the csv module leaves to you.

How do I read a very large CSV file without using too much memory?

Stream the file by iterating over csv.DictReader rather than calling list() or read(), which loads the whole thing at once. Process one row at a time and write qualifying results straight to a second file as you go. If a single cell trips the 128 KB limit, raise it with csv.field_size_limit(). In pandas, read_csv(…, chunksize=10000) yields the same streaming behaviour.

Why do I get blank lines between rows when I write a CSV file in Python?

It is almost always the missing newline argument. On Windows, Python translates every line ending you write into a carriage return and line feed pair, and translating again on the next read produces a second break. The csv module manages line endings itself, so pass newline=” when you open the file in either read or write mode and the blank lines disappear.

What is the difference between writerow() and writerows() in Python?

writerow() takes a single row, such as a list or a dictionary, and writes one record. writerows() takes an iterable of rows and writes them all, looping internally. The output is identical; the difference is call count and readability. Write the header with writerow(), then hand the remaining rows to writerows() once.

Conclusion

Start with csv.DictReader for reading and csv.DictWriter for anything shaped like a dictionary, and drop to csv.reader and csv.writer when your rows are plain lists. Open every file with newline="" and encoding="utf-8", keep the work inside a with block, and let the module handle quoting.

That is how to read and write CSV files in Python without a single dependency. Move to pandas the day you need filtering, joins or a file bigger than memory, and keep coming back here for the small jobs where a DataFrame is overkill.

Leave a Comment