Python CSV Tutorial: Read, Write, Append and Filter CSV Files with the csv Module

CSV (comma-separated values) files are everywhere: exported Excel sheets, Google Forms responses, bank statements, grade sheets, product lists for an inventory system. Sooner or later every programmer has to read one, clean it, compute something and write a new file. In Python you do not need to install anything for that. The built-in csv module handles it, including the tricky parts like commas inside a field, quotes, different delimiters and Excel-friendly encodings.

In this tutorial you will learn how to write, read, append and filter CSV files in Python using csv.writer, csv.reader, csv.DictReader and csv.DictWriter. We will build a small student grades example and generate a pass/fail report from it. Every script below was run on Python 3.13, and the output is copied from the terminal.

Requirements

  • Python 3.8 or newer (any current version works; the csv module has been stable for years).
  • A code editor such as VS Code, PyCharm or IDLE.
  • Put all the scripts in the same folder and run them in order, because each one uses the file created by the previous one.

Step 1: Write a CSV File with csv.writer

csv.writer turns Python lists into CSV lines. writerow() writes one row and writerows() writes many. Save this as 1_write_csv.py:

  1. import csv
  2.  
  3. rows = [
  4.     ["student_id", "name", "course", "math", "science", "english"],
  5.     ["2026-001", "Ana Santos", "BSIT", 92, 88, 95],
  6.     ["2026-002", "Ben Cruz", "BSCS", 78, 85, 80],
  7.     ["2026-003", "Carla Reyes", "BSIT", 85, 91, 89],
  8.     ["2026-004", "Dan Lim", "BSIS", 70, 74, 68],
  9. ]
  10.  
  11. # newline="" is important: without it, Windows adds an empty line between rows
  12. with open("students.csv", "w", newline="", encoding="utf-8") as f:
  13.     writer = csv.writer(f)
  14.     writer.writerows(rows)
  15.  
  16. print("students.csv created")
  17. with open("students.csv", encoding="utf-8") as f:
  18.     print(f.read())

Run it with python 1_write_csv.py. Output:

students.csv created
student_id,name,course,math,science,english
2026-001,Ana Santos,BSIT,92,88,95
2026-002,Ben Cruz,BSCS,78,85,80
2026-003,Carla Reyes,BSIT,85,91,89
2026-004,Dan Lim,BSIS,70,74,68

Two habits from this example are worth keeping in every CSV script:

  • newline="": the csv module writes its own line endings. If you leave this out on Windows, you get an empty row between every record when you open the file in Excel. The official Python documentation recommends newline="" for both reading and writing.
  • encoding="utf-8": without it, Python uses your system's default encoding, which on some Windows machines breaks names like "Peña".

Step 2: Read a CSV File with csv.reader

csv.reader gives you each line as a list of strings. Use next() to take the header row first:

  1. import csv
  2.  
  3. with open("students.csv", newline="", encoding="utf-8") as f:
  4.     reader = csv.reader(f)
  5.     header = next(reader)          # first row is the header
  6.     print("Columns:", header)
  7.     for row in reader:
  8.         print(row)
  9.  
  10. # Note: csv.reader always gives you strings
  11. print(type(row[3]), row[3])

Run it with python 2_read_csv.py. Output:

Columns: ['student_id', 'name', 'course', 'math', 'science', 'english']
['2026-001', 'Ana Santos', 'BSIT', '92', '88', '95']
['2026-002', 'Ben Cruz', 'BSCS', '78', '85', '80']
['2026-003', 'Carla Reyes', 'BSIT', '85', '91', '89']
['2026-004', 'Dan Lim', 'BSIS', '70', '74', '68']
<class 'str'> 70

Notice the last line: every value is a string, even the scores. CSV has no data types, so you must convert numbers yourself with int() or float() before doing math.

Step 3: Read Rows as Dictionaries with csv.DictReader

Using positions like row[3] is hard to read and breaks when someone adds a column. csv.DictReader uses the header row as keys, so you can write student["math"] instead. Here we compute each student's average:

  1. import csv
  2.  
  3. with open("students.csv", newline="", encoding="utf-8") as f:
  4.     for student in csv.DictReader(f):
  5.         scores = [int(student["math"]), int(student["science"]), int(student["english"])]
  6.         average = sum(scores) / len(scores)
  7.         print(f"{student['name']:<12} {student['course']:<5} average: {average:.2f}")

Run it with python 3_dictreader.py. Output:

Ana Santos   BSIT  average: 91.67
Ben Cruz     BSCS  average: 81.00
Carla Reyes  BSIT  average: 88.33
Dan Lim      BSIS  average: 70.67

This is the style you should use most of the time. The code explains itself and keeps working even if the columns are reordered.

Step 4: Append a Row with csv.DictWriter

To add records to an existing file without erasing it, open it in append mode ("a"). csv.DictWriter writes dictionaries, and fieldnames tells it the column order:

  1. import csv
  2.  
  3. fields = ["student_id", "name", "course", "math", "science", "english"]
  4. new_student = {"student_id": "2026-005", "name": "Ella Garcia",
  5.                "course": "BSCS", "math": 88, "science": 79, "english": 93}
  6.  
  7. # "a" = append mode: adds rows to the end without deleting existing data
  8. with open("students.csv", "a", newline="", encoding="utf-8") as f:
  9.     writer = csv.DictWriter(f, fieldnames=fields)
  10.     writer.writerow(new_student)
  11.  
  12. with open("students.csv", newline="", encoding="utf-8") as f:
  13.     print(sum(1 for _ in csv.DictReader(f)), "students in the file now")

Run it with python 4_dictwriter_append.py. Output:

5 students in the file now

Be careful with the mode: "w" erases the file before writing, while "a" adds to the end. Also, do not call writeheader() when appending, or you will end up with a second header row in the middle of your data.

Step 5: Read, Compute and Write a New Report

A very common task is to read one CSV, process each row and save the result to another CSV. You can open both files in a single with statement. After that, we filter the report to BSIT students only and rank them by average:

  1. import csv
  2.  
  3. fields = ["student_id", "name", "course", "average", "remarks"]
  4.  
  5. with open("students.csv", newline="", encoding="utf-8") as src, \
  6.      open("report.csv", "w", newline="", encoding="utf-8") as dst:
  7.     writer = csv.DictWriter(dst, fieldnames=fields)
  8.     writer.writeheader()
  9.     for s in csv.DictReader(src):
  10.         avg = round((int(s["math"]) + int(s["science"]) + int(s["english"])) / 3, 2)
  11.         writer.writerow({
  12.             "student_id": s["student_id"],
  13.             "name": s["name"],
  14.             "course": s["course"],
  15.             "average": avg,
  16.             "remarks": "PASSED" if avg >= 75 else "FAILED",
  17.         })
  18.  
  19. with open("report.csv", encoding="utf-8") as f:
  20.     print(f.read())
  21.  
  22. # Keep only BSIT students, sorted by average (highest first)
  23. with open("report.csv", newline="", encoding="utf-8") as f:
  24.     bsit = [r for r in csv.DictReader(f) if r["course"] == "BSIT"]
  25. bsit.sort(key=lambda r: float(r["average"]), reverse=True)
  26. for rank, r in enumerate(bsit, start=1):
  27.     print(rank, r["name"], r["average"])

Run it with python 5_filter_report.py. Output:

student_id,name,course,average,remarks
2026-001,Ana Santos,BSIT,91.67,PASSED
2026-002,Ben Cruz,BSCS,81.0,PASSED
2026-003,Carla Reyes,BSIT,88.33,PASSED
2026-004,Dan Lim,BSIS,70.67,FAILED
2026-005,Ella Garcia,BSCS,86.67,PASSED

1 Ana Santos 91.67
2 Carla Reyes 88.33

Because DictReader gives strings, the sort key converts the average with float(). Sorting the strings directly would compare them character by character, so an average of "9.5" would be ranked above "81.0".

Step 6: Commas, Quotes and Other Delimiters

What if a value contains a comma, like "Notebook, 80 leaves"? If you build CSV lines yourself with ",".join(...), that comma splits the field in two and corrupts the file. The csv module solves this automatically by wrapping such fields in quotes, and it doubles any quote characters inside a field:

  1. import csv
  2.  
  3. products = [
  4.     ["code", "description", "price"],
  5.     ["P-100", 'Notebook, 80 leaves', 35.50],
  6.     ["P-200", 'Ballpen "Fine Point" Blue', 12.00],
  7. ]
  8.  
  9. with open("products.csv", "w", newline="", encoding="utf-8") as f:
  10.     csv.writer(f).writerows(products)
  11.  
  12. print(open("products.csv", encoding="utf-8").read())
  13.  
  14. # The csv module un-quotes the fields for you when reading
  15. with open("products.csv", newline="", encoding="utf-8") as f:
  16.     for row in csv.reader(f):
  17.         print(row)
  18.  
  19. # Semicolon-separated file (common in Excel set to European regional settings)
  20. with open("products_semicolon.csv", "w", newline="", encoding="utf-8") as f:
  21.     csv.writer(f, delimiter=";").writerows(products)
  22. print(open("products_semicolon.csv", encoding="utf-8").read())

Run it with python 6_quotes_delimiters.py. Output:

code,description,price
P-100,"Notebook, 80 leaves",35.5
P-200,"Ballpen ""Fine Point"" Blue",12.0

['code', 'description', 'price']
['P-100', 'Notebook, 80 leaves', '35.5']
['P-200', 'Ballpen "Fine Point" Blue', '12.0']
code;description;price
P-100;Notebook, 80 leaves;35.5
P-200;"Ballpen ""Fine Point"" Blue";12.0

Use delimiter=";" (or "\t" for tab-separated files) on both the writer and the reader when your file does not use commas. In the semicolon version, the notebook description no longer needs quotes because it does not contain a semicolon.

Step 7: Make CSV Files That Open Correctly in Excel

If you open a UTF-8 CSV by double-clicking it, Excel may show "Peña" instead of "Peña". The fix is the utf-8-sig encoding, which adds a small marker called a BOM (byte order mark) at the start of the file so Excel knows it is UTF-8:

  1. import csv
  2.  
  3. rows = [["name", "city"], ["Jose Peña", "Parañaque"], ["Zoë Müller", "Köln"]]
  4.  
  5. # utf-8-sig adds a BOM so Microsoft Excel shows ñ, ë and ü correctly
  6. with open("excel_friendly.csv", "w", newline="", encoding="utf-8-sig") as f:
  7.     csv.writer(f).writerows(rows)
  8.  
  9. print(open("excel_friendly.csv", "rb").read()[:3])
  10.  
  11. # Reading with utf-8-sig also removes the BOM, so the first header is "name"
  12. with open("excel_friendly.csv", newline="", encoding="utf-8-sig") as f:
  13.     print(next(csv.DictReader(f)))
  14.  
  15. # Reading the same file with plain utf-8 keeps the BOM in the first key
  16. with open("excel_friendly.csv", newline="", encoding="utf-8") as f:
  17.     print(list(next(csv.DictReader(f)).keys()))

Run it with python 7_excel_utf8.py. Output:

b'\xef\xbb\xbf'
{'name': 'Jose Peña', 'city': 'Parañaque'}
['\ufeffname', 'city']

The last line shows a classic bug: when a file has a BOM and you read it with plain utf-8, the first column name becomes '\ufeffname', so row["name"] raises a KeyError. If your CSV comes from Excel and the first column "does not exist", open it with encoding="utf-8-sig".

csv Module or pandas?

The pandas library can also read CSV files with pd.read_csv(), and it is great for data analysis, charts and very large calculations. But it is a big dependency. For scripts that import, export or clean records, such as uploading a class list into your database or exporting a sales report from your capstone system, the built-in csv module is lighter, has nothing to install, and gives you full control over each row.

Quick Reference

TaskUse
Read rows as listscsv.reader(f)
Read rows as dictionariescsv.DictReader(f)
Write listscsv.writer(f).writerow(row) / writerows(rows)
Write dictionariescsv.DictWriter(f, fieldnames=[...]) + writeheader()
Add to an existing fileopen(path, "a", newline="")
Semicolon or tab filesdelimiter=";" or delimiter="\t"
Excel-friendly UTF-8encoding="utf-8-sig"

Conclusion

You now know how to handle CSV files in Python the right way: write them with csv.writer and DictWriter, read them with reader and DictReader, append without losing data, convert strings to numbers before doing math, and avoid the common traps with commas, quotes, blank lines and Excel encodings. Try it with a real export from your own project, such as generating a grade report or importing a product list. You can download all seven example scripts below.

Happy coding!