Read a CSV as dictionaries
Reads a CSV file into a list of dictionaries keyed by header row, with the encoding and newline handling that avoids the usual gotchas.
Last updated
import csv
def read_csv_dicts(path):
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
# rows = read_csv_dicts("users.csv")
# rows[0]["email"]How it works
csv.DictReader turns the first row into field names and every row after it into a dict keyed by those names, so callers read rows[i]["email"] by column name instead of tracking a fragile positional index that breaks the moment a column gets reordered.
The two open() arguments are both there for a specific bug each: newline="" hands the file to the csv module exactly as Python's own docs recommend, so it (not the platform's universal-newlines layer) is the one deciding how embedded newlines inside quoted fields are handled; encoding="utf-8" is stated explicitly rather than left to the platform default, which is ASCII-ish on some Windows locales and would otherwise raise on the first non-ASCII name or accented character.
Edge cases to know
- →list(...) reads the whole file into memory at once — fine for typical CSVs, but for a multi-gigabyte file, iterate the DictReader directly (for row in csv.DictReader(f): ...) instead of collecting it into a list.
- →A CSV saved with a different encoding (e.g. Windows-1252 from older Excel exports) will raise a UnicodeDecodeError with encoding="utf-8" — pass the actual source encoding, or encoding="utf-8-sig" if the file has a BOM.
- →Rows with more or fewer fields than the header get padded with None or collected into a list under the None key rather than raising — check for that key if the source data isn't guaranteed clean.
Part by part
- newline=""
- Required by the csv module. It disables Python's universal newline translation so the reader can handle line breaks inside quoted fields itself.
- encoding="utf-8"
- Explicit rather than relying on the platform default, which on Windows is still often cp1252 and mangles any non-ASCII character.
- csv.DictReader(f)
- Reads the first row as field names and yields each subsequent row as a dictionary keyed by them.
- list(
- Materialises the rows before the with block closes. DictReader is lazy, so returning it directly would hand back a reader over a closed file.
Common mistakes
with open(path) as f: return csv.DictReader(f)The file closes when the with block exits, so iterating the returned reader raises ValueError about an operation on a closed file. Either materialise inside the block or make the function a generator.
with open(path, newline="", encoding="utf-8") as f: return list(csv.DictReader(f))open(path, encoding="utf-8")Omitting newline="" lets Python translate line endings before the csv module sees them, which breaks any field containing an embedded newline — exactly the case quoting exists for.
rows = [line.split(",") for line in open(path)]Splitting on commas ignores quoting, so a field containing a comma splits into two and every column after it shifts. It works on simple data right up until someone enters an address.
Questions
How do I handle a file with no header row?
Pass fieldnames explicitly to DictReader, and it treats the first row as data. Without that it consumes your first record as column names and silently loses it.
What if the file uses semicolons?
Pass delimiter=";". Several European locales export that way because the comma is the decimal separator, and csv.Sniffer can detect it if the format varies between files.
Why do I get a BOM in my first column name?
Because Excel writes UTF-8 with a byte-order mark. Use encoding="utf-8-sig", which strips it — otherwise your first key is invisibly prefixed and every lookup on it fails.
Should I use pandas instead?
For analysis, yes. For reading a file once, the csv module has no dependency, no startup cost and no type inference guessing at your columns — which is often exactly what you want.