Python Write List to CSV: Header, Rows and Columns
Save Python lists as a CSV file with the built-in csv module: a header row, list of lists, one column, dicts, no blank lines, appending, and Excel-friendly files.
Read and write CSV in Python by column name with csv.DictReader and DictWriter. newline='', Excel BOM, missing columns, extrasaction and real examples.
The plain csv.reader gives you each row as a list, so you end up writing row[3] and hoping column 3 never moves. csv.DictReader and csv.DictWriter fix that: every row is a dictionary keyed by the header, so your code says row["email"]. Both are in the standard library, so there is nothing to install.
If you only need the basics, reading a CSV in Python without pandas covers csv.reader. This post goes deeper into the dictionary versions.
import csv
with open("customers.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["email"])
The first line of the file becomes the keys. Every value is a string, so convert numbers yourself: int(row["age"]).
The Python docs say to open CSV files with newline="". Without it, quoted fields that contain line breaks can be read wrongly, and on Windows the writer adds blank lines between rows. It is the single most common CSV bug I see.
Excel often saves "CSV UTF-8" with a byte order mark. The first header then reads as "\ufeffname" and row["name"] raises KeyError. Open the file with encoding="utf-8-sig", which strips the BOM if present and works normally if not.
reader = csv.DictReader(f, fieldnames=["name", "email", "plan"])
When you pass fieldnames, the first line is treated as data, not as a header.
restval (default None).restkey (default None).reader = csv.DictReader(f, restval="", restkey="extra")
rows = [
{"name": "Amira", "email": "amira@example.com", "plan": "pro"},
{"name": "Tom", "email": "tom@example.co.uk", "plan": "free"},
]
with open("out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "email", "plan"])
writer.writeheader()
writer.writerows(rows)
fieldnames sets the column order. Forgetting writeheader() gives you a file with no header line.
By default extrasaction="raise", so any key that is not in fieldnames stops the write. That is a good safety net. If you deliberately want to drop extra keys (for example an internal id), pass extrasaction="ignore". Missing keys are written as restval, an empty string by default.
import csv
with open("orders.csv", newline="", encoding="utf-8-sig") as src, \
open("big_orders.csv", "w", newline="", encoding="utf-8") as dst:
reader = csv.DictReader(src)
writer = csv.DictWriter(dst, fieldnames=reader.fieldnames + ["vat"])
writer.writeheader()
for row in reader:
total = float(row["total"])
if total >= 100:
row["vat"] = f"{total / 6:.2f}" # VAT inside a 20% VAT-inclusive price
writer.writerow(row)
Dividing a VAT-inclusive price by 6 gives the 20% VAT part. For formatting tips see Python f-string formatting.
Many European exports use ;. Pass delimiter=";" to both reader and writer. If you do not know the format, csv.Sniffer().sniff(sample) can guess it from the first few kilobytes.
import csv, json
with open("customers.csv", newline="", encoding="utf-8-sig") as f:
data = list(csv.DictReader(f))
with open("customers.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
More in writing pretty JSON in Python. For a quick one-off you can also paste the file into my free CSV to JSON converter, which runs in your browser.
Slightly, because it builds a dictionary per row. For files up to a few hundred thousand rows the difference rarely matters.
Use reader.fieldnames. It is filled once the first row has been read, or as soon as you access it.
Use pandas for analysis, grouping and large numeric work. For simple read, filter and write jobs, the csv module is lighter and has no dependency.
Use csv.writer, shown in Python write list to CSV.