LambdaLabTM
Computer Science · Class 12 · CSV Files
CSV filesreading⏱️ 13 min read

reader()

Writing had two calls; reading has one. Wrap the open file in csv.reader() and loop over it, and each line of the file arrives as a list — already split, with the quoting sorted out. There is exactly one thing it does not do for you, and it costs marks every year.

1csv.reader() — wrapping the file

r = csv.reader(file)

One argument — the file open() gave you, in 'r'. What comes back is the reader.

Reading marks.csv, the file written in the last two lessons:

read_marks.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

for row in r:
    print(row)

f.close()
Output
['Roll', 'Name', 'Marks']
['1', 'Ravi', '78']
['2', 'Meera', '91']
['3', 'Amit', '65']
r = csv.reader(f)

Wraps the file. Nothing is read yet — this line alone prints nothing and moves no pointer.

for row in r:

The loop is what reads. Each turn brings back the next line of the file, already split into a list.

print(row)

row is an ordinary Python list, so row[0], len(row) and slicing all work exactly as you would expect.

Note
You can write it in one line — for row in csv.reader(f): — and most board answers do. The two-line version is easier to read while you are learning, and neither is wrong.

2Every value comes back as a string

Look at that output again. The marks are '78', not 78. They were written as numbers, and they have come back as text:

strings.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)
next(r)

for row in r:
    print(row[2], type(row[2]))
    print(row[2] + 10)

f.close()
Output
78 <class 'str'>
Traceback (most recent call last):
  File "strings.py", line 9, in <module>
    print(row[2] + 10)
TypeError: can only concatenate str (not "int") to str
Key Takeaway
A CSV file is a text file. Text is the only thing it can hold, so the module has no way of knowing that 78 was ever a number. Anything you want to do arithmetic with needs int() or float() first. This is not a quirk of the csv module — it is the same rule as input(), and the same rule as reading a text file.
total.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)
header = next(r)

total = 0
for row in r:
    total = total + int(row[2])

f.close()

print('Header was:', header)
print('Total marks:', total)
Output
Header was: ['Roll', 'Name', 'Marks']
Total marks: 234

The reader hands back every line, and the first one is usually the header. Try to add it in with the marks and you get ValueError: invalid literal for int() with base 10: 'Marks'. The usual way past it is a built-in function called next(), and it is worth understanding rather than copying, because it is not really a “skip” instruction at all.

next(r)

Reads exactly one row, moves the pointer past it, and returns that row.

Two things happen on every call, and they are separate. It reads a row — that is a change to where the file pointer is sitting. And it gives that row back — that is a value, which you may catch or may not.

next_returns.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

first = next(r)
print(first)
print(type(first))

f.close()
Output
['Roll', 'Name', 'Marks']
<class 'list'>

A list, exactly like the ones the loop gives you — because it is the same row, fetched the same way. Call it again and you get the one after it, because the pointer moved:

next_thrice.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

print(next(r))
print(next(r))
print(next(r))

f.close()
Output
['Roll', 'Name', 'Marks']
['1', 'Ravi', '78']
['2', 'Meera', '91']
A for loop is next() over and over
This is what for row in r: has been doing all along — calling next() once per turn and putting the answer in row. That is the whole reason a next() before the loop shifts the loop's starting point: the row has already been taken, so the loop begins at the one after it.

4Skipping is just ignoring the answer

Here is the part that confuses people. There is no “skip” function in Python. To skip a row you use the reading half of next() and simply do not keep what it hands back:

next_discard.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

next(r)          # read it, keep nothing

for row in r:
    print(row)

f.close()
Output
['1', 'Ravi', '78']
['2', 'Meera', '91']
['3', 'Amit', '65']

next(r) sits on a line by itself, with no = in front of it. Python runs it, the header is read, and the list it returns is thrown away because nothing was waiting to catch it. The header never reaches the loop. It was not deleted — it was read past.

keep it — you want the column names
header = next(r)
print(header[1])   # Name

Useful when you print a heading above the table, as the practice page does.

drop it — you only want it out of the way
next(r)

Same reading, same pointer move. The only difference is that nobody caught the answer.

Both lines skip the header equally well
Writing header = next(r) and never using header is not a mistake — it costs one unused variable and nothing else. Marks are given for either.

5Skipping more than one row

One call, one row. Two rows to get rid of means two calls — there is nothing to add and no count to pass:

skip_two.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

next(r)     # the header
next(r)     # and Ravi's row as well

for row in r:
    print(row)

f.close()
Output
['2', 'Meera', '91']
['3', 'Amit', '65']

The same idea works anywhere, not only at the top of the file. A next(r) in the middle of a loop reads one extra row and drops it, so the loop skips it.

6Two things to watch

Only if the file HAS a header
next(r) reads past the first line whatever it is. On a file with no header row it quietly swallows your first record, and your count comes out one short. Look at the file before you write the loop.

And next() needs a row to be there. Ask for one when the file is finished and it does not return an empty list — it raises:

next_too_far.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

next(r)
next(r)
next(r)
next(r)
next(r)
Output
Traceback (most recent call last):
  File "next_too_far.py", line 10, in <module>
    next(r)
StopIteration

Four rows in the file, so four calls work and the fifth has nothing left. StopIteration is the signal a for loop catches for you — it is how the loop knows to stop. Called by hand, nobody catches it, so it reaches the screen. In practice this only bites on an empty file, where there is not even a header to skip.

7The other way you will see it written

Some answers skip the header with a counter instead. It works, it is longer, and it is worth recognising in someone else's code:

with_a_counter.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

n = 0
for row in r:
    n = n + 1
    if n == 1:
        continue
    print(row)

f.close()
Output
['1', 'Ravi', '78']
['2', 'Meera', '91']
['3', 'Amit', '65']

Five lines doing what next(r) does in one. Use next().

8A reader can only be looped once

The loop moves the file pointer to the end. Loop a second time and there is nothing left in front of it:

used_up.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

print('first loop:')
for row in r:
    print(row)

print('second loop:')
for row in r:
    print(row)

print('done')
f.close()
Output
first loop:
['Roll', 'Name', 'Marks']
['1', 'Ravi', '78']
['2', 'Meera', '91']
['3', 'Amit', '65']
second loop:
done

No error — the second loop simply runs zero times. This is the exact behaviour read() had in the Text Files chapter, and the repair is the same one: send the pointer back with seek(0).

seek_back.py
import csv

f = open('marks.csv', 'r', newline='')
r = csv.reader(f)

for row in r:
    print(row)

f.seek(0)
print('--- again ---')
for row in r:
    print(row)

f.close()
Output
['Roll', 'Name', 'Marks']
['1', 'Ravi', '78']
['2', 'Meera', '91']
['3', 'Amit', '65']
--- again ---
['Roll', 'Name', 'Marks']
['1', 'Ravi', '78']
['2', 'Meera', '91']
['3', 'Amit', '65']
Or keep the rows
If you need the data more than once, read it into a list and work from that: rows = list(csv.reader(f)). A list can be looped as often as you like, and every update and delete program in this chapter is built on exactly that line.
as_a_list.py
import csv

f = open('marks.csv', 'r', newline='')
data = list(csv.reader(f))
f.close()

print(data)
print(len(data))
print(data[2][1])
Output
[['Roll', 'Name', 'Marks'], ['1', 'Ravi', '78'], ['2', 'Meera', '91'], ['3', 'Amit', '65']]
4
Meera

Note len(data) is 4, not 3 — the header is one of the rows. Counting records means subtracting it, or skipping it before you count.

9Read a file you wrote yourself

reading_playground.py

10Reading and writing, side by side

Let's Recap!
csv.writer(f)csv.reader(f)
File mode'w' or 'a''r'
What you give itthe open filethe open file
How you use itw.writerow(row) / w.writerows(rows)for row in r:
Directionlist → lineline → list
Valuesnumbers accepted as they areeverything comes back as str
Reusablecall writerow() as often as you likeone loop only, unless you seek(0)

11Recap

r = csv.reader(f)

Wraps the open file. Reads nothing by itself — the loop is what reads.

Each row is a list

Already split on the commas, with any quoting removed. row[0], len(row) and slicing all work.

Every value is a str

'78', not 78. int() before any arithmetic, or you get TypeError.

next(r) reads one row and returns it

Two separate things: the pointer moves past that row, and the row is handed back. A for loop is next() called once per turn.

Skipping = not catching the answer

next(r) on a line of its own reads the header and throws the value away. It was not deleted, just read past. Two rows to skip means two calls.

next() past the end raises

StopIteration — the signal a for loop catches for you. Only bites on an empty file, where there is no header to skip.

One loop per reader

The second loop runs zero times. f.seek(0) sends the pointer back to the start.

list(csv.reader(f))

All the rows in a list, loopable as often as you like — and the header is one of them, so len() is one more than you expect.

✍️ Now write these yourself
  1. 1

    Print only the names from a CSV of student records.

    Hint · row[1], once you have skipped the header with next().

  2. 2

    Add up a column of marks, first without int() and then with it.

    Hint · Without it you get either a TypeError or a very long string, depending on how you started the total.

  3. 3

    Put next(r) in a variable and print it, then run the same program with the variable taken away.

    Hint · The loop starts at the same row either way. Catching the answer changes nothing about the skipping.

  4. 4

    Call next(r) one more time than the file has rows.

    Hint · StopIteration. There was nothing left to read.

  5. 5

    Loop over the same reader twice and count what you get.

    Hint · Four rows, then none. Add f.seek(0) between them.

  6. 6

    Read the file into a list and print len() — then work out how many records that is.

    Hint · One fewer than the length. The header is a row too.

Quick Check

What type is each row that csv.reader() hands back?

Quick Check

row[2] holds '78'. What does row[2] + 10 do?

Quick Check

What does next(r) do?

Quick Check

Why does writing next(r) on a line by itself skip a row?

Quick Check

A file has a header and you want to skip the header AND the first record. What do you write?

Quick Check

You loop over a reader, then loop over it again. What does the second loop print?