Class 12Computer Science · Programming with PythonFull chapter

CSV Files

The whole chapter in one place — read it, then test yourself. Clear notes, a reference sheet, a practice quiz, and worked NCERT solutions & PYQs.

What a CSV File Is, and Why the csv Module

Quick answer A CSV file is a plain text file where each line is one record and commas separate the fields, and you import the csv module rather than joining strings yourself because the module handles the quoting rules that silently corrupt hand-built lines.

A CSV file (Comma Separated Values) is an ordinary text file that stores a table. Each line is one record, and inside a line the fields are separated by commas.

Roll,Name,Marks
1,Aarav Sharma,88
2,Diya Nair,92

That is the entire format. Open the file in Notepad and you see text; open the very same file in Excel or Google Sheets and you see a spreadsheet. This is why almost every system hands you data as CSV — your school's marksheet export, a bank statement download, IRCTC booking history, a UPI transaction report. CSV is the plain-text handshake between two programs that share nothing else.

Where CSV sits among the file types in this unit:

FeatureText file (.txt)CSV file (.csv)Binary file (.dat)
Readable by a humanYesYesNo
StructureNone — free textRows, comma-separated fieldsWhatever pickle stored
Opens inNotepadNotepad and ExcelNeither
Module neededNonecsvpickle
Type you get on reading backstrstr, alwaysOriginal type preserved
Mode strings'r', 'w', 'a''r', 'w', 'a''rb', 'wb', 'ab'

Now the question that actually matters. If a CSV is nothing but commas and newlines, why import csv at all? Why not build each line yourself with ",".join(...)?

Because of one address. Watch what happens when a field itself contains a comma.

import csv

rows = [["Aarav Sharma", "Mumbai, Maharashtra", 45000],
        ["Diya Nair", "Kochi", 38000]]

# Way 1: joining strings by hand
f = open("hand.csv", "w")
for r in rows:
    f.write(",".join(str(x) for x in r) + "\n")
f.close()

# Way 2: the csv module
f = open("safe.csv", "w", newline="")
csv.writer(f).writerows(rows)
f.close()

print("hand.csv:", repr(open("hand.csv").read()))
print("safe.csv:", repr(open("safe.csv").read()))

print("\nReading hand.csv back:")
for rec in csv.reader(open("hand.csv")):
    print(len(rec), "fields ->", rec)

print("\nReading safe.csv back:")
for rec in csv.reader(open("safe.csv", newline="")):
    print(len(rec), "fields ->", rec)

Real output:

hand.csv: 'Aarav Sharma,Mumbai, Maharashtra,45000\nDiya Nair,Kochi,38000\n'
safe.csv: 'Aarav Sharma,"Mumbai, Maharashtra",45000\nDiya Nair,Kochi,38000\n'

Reading hand.csv back:
4 fields -> ['Aarav Sharma', 'Mumbai', ' Maharashtra', '45000']
3 fields -> ['Diya Nair', 'Kochi', '38000']

Reading safe.csv back:
3 fields -> ['Aarav Sharma', 'Mumbai, Maharashtra', '45000']
3 fields -> ['Diya Nair', 'Kochi', '38000']

Look carefully at what broke. In hand.csv the address Mumbai, Maharashtra went in raw, so the comma inside the address became a separator. A record written with 3 fields came back with 4. Aarav's salary is now in field 3, where the state name should be. Every column after the damage is shifted by one — and Python raised no error at any point. That is the worst kind of bug: silent, and only discovered weeks later when someone's salary looks wrong.

The csv module saw the comma and wrapped the field in double quotes by itself: "Mumbai, Maharashtra". On the way back, reader() understood those quotes and returned 3 fields. You wrote no quoting logic at all. That is the whole reason the module exists — it already knows the escaping rules, so you never have to.

Every CSV program has the same three-step shape. First import csv. Then open the file with open(), choosing the mode: 'r' to read, 'w' to create or overwrite, 'a' to add to the end. Then close it with f.close(), or let a with block close it for you. Until the file is closed, what you wrote may still be sitting in a buffer and the file on disk can be empty.

One detail in the code above is not decoration: the newline="" in the open() call. Leave it out on Windows and every record picks up an extra carriage return, so a blank line appears between rows and csv.reader() hands you twice as many rows as you wrote. Section 3 proves it byte by byte — start typing newline="" now so it becomes a reflex.

One matching caution, so the reflex does not become superstition: newline="" belongs on the file object you are about to hand to csv.writer() or csv.reader(). When you only want to dump a file to the screen with print(open("data.csv").read()), plain open() is the right call — Section 3 shows why.

Import the module import csv Part of the standard library — nothing to install. Gives you csv.writer() and csv.reader().
Open to read f = open("data.csv", "r", newline="") 'r' is the default mode. Raises FileNotFoundError if the file does not exist.
Open to create / overwrite f = open("data.csv", "w", newline="") Creates the file, or empties an existing one the instant it opens — before you write anything.
Open to append f = open("data.csv", "a", newline="") Adds at the end, and creates the file if it is missing. Never erases existing records.
Close the file f.close() Flushes the buffer to disk. Read the file before closing and you may get an empty string.
Auto-closing form with open("data.csv", "w", newline="") as f: Closes the file even if an error occurs inside the block. Preferred in board answers.
Remember
  • A CSV file is a plain text file: one line per record, commas between fields. Notepad and Excel both open it, which is why it is the standard export format everywhere.
  • Building CSV lines by hand with ",".join() breaks the moment a field contains a comma — a 3-field record came back as 4 fields with no error raised.
  • csv.writer() automatically wraps any field containing a comma in double quotes, and csv.reader() understands those quotes on the way back. That automatic quoting is the reason to use the module.
  • Reading any CSV file always returns strings, never numbers — unlike a binary/pickle file, which preserves the original type.
  • Pass newline="" to open() whenever the file object is going to a csv.writer() or csv.reader(), and always close the file (or use a with block) before reading it back.

Writing Records: writer() and writerow()

Quick answer csv.writer(fileobject) returns a writer object whose writerow(sequence) method writes exactly one record and returns the number of characters written, with mode 'w' truncating the file on open and mode 'a' appending safely.

Writing a CSV takes two objects, and students routinely confuse them.

  1. The file object, returned by open(). This is the connection to the disk.
  2. The writer object, returned by csv.writer(fileobject). This is the thing that knows the CSV rules — commas, quotes, line endings.

The writer does not open or close anything. It only formats. You still open the file yourself and you still close it yourself. csv.writer() takes the file object as its argument, so the file must already be open in 'w' or 'a' mode before you call it.

writerow() takes one sequence — a list or a tuple — and writes it as one line of the file. Numbers are converted to text for you, so you never call str() on them.

The mode you choose decides whether yesterday's data survives. 'w' truncates the file to zero bytes the moment open() runs; 'a' positions you at the end and adds. This program shows both, plus the return value of writerow(), which is not what most students expect.

import csv

# STEP 1 - create the file with 'w'
f = open("fees.csv", "w", newline="")
wr = csv.writer(f)
n = wr.writerow(["Roll", "Name", "FeePaid"])
print("writerow() returned:", n)
wr.writerow([1, "Aarav Sharma", 12000])
wr.writerow([2, "Diya Nair", 9500])
f.close()
print("after 'w':")
print(open("fees.csv").read())

# STEP 2 - add one more with 'a'
f = open("fees.csv", "a", newline="")
csv.writer(f).writerow([3, "Kabir Menon", 15000])
f.close()
print("after 'a':")
print(open("fees.csv").read())

# STEP 3 - opening with 'w' again destroys everything
f = open("fees.csv", "w", newline="")
csv.writer(f).writerow([99, "Oops", 0])
f.close()
print("after 'w' again:")
print(open("fees.csv").read())

Real output:

writerow() returned: 19
after 'w':
Roll,Name,FeePaid
1,Aarav Sharma,12000
2,Diya Nair,9500

after 'a':
Roll,Name,FeePaid
1,Aarav Sharma,12000
2,Diya Nair,9500
3,Kabir Menon,15000

after 'w' again:
99,Oops,0

Three things to take from that output.

writerow() returned 19, not None. The documented rule is that writerow() hands back whatever the underlying file object's write() method returns, and for a text file that is the number of characters written: Roll,Name,FeePaid is 17 characters plus the two-character line ending \r\n, giving 19. You will almost never use this value, but it is a favourite one-mark trap, because the instinctive answer — None — is wrong. (writerows(), covered in Section 4, genuinely does return None; the pair makes a neat exam question.)

Mode 'a' preserved all three earlier records and added the fourth. This is the mode every "write a function to add a record" board question needs.

Mode 'w' the second time wiped the file down to a single row. Nothing warned you. If your program opens a data file with 'w' every time it runs, it destroys the previous run's data on startup — a real bug that has cost students marks and companies data.

Note also that the writer accepted a mixed list — an int roll number, a str name, an int amount — and wrote them all as text without complaint. A tuple works exactly the same as a list. What does not work the way you expect is passing a bare string: writerow("Delhi") treats the string as a sequence of five characters and writes D,e,l,h,i as five separate fields. Always wrap a single value in a list: writerow(["Delhi"]).

Create a writer wr = csv.writer(f) f must be a file object opened in a text mode ('w' or 'a'). On a file opened 'wb' the writer is still created, but the first writerow() raises TypeError: a bytes-like object is required, not 'str'.
Write one record wr.writerow(["Roll", "Name", "Marks"]) Returns the count of characters written. One call = one line in the file.
Tuples work too wr.writerow((1, "Aarav", 88)) Any sequence is accepted. int and float fields are converted to text for you.
Overwrite mode open("f.csv", "w", newline="") Truncates to 0 bytes on open, before any write. Re-running the program destroys the earlier data.
Append mode open("f.csv", "a", newline="") Adds at the end; creates the file if it does not exist. The mode for an ADD() function.
Gotcha: bare string wr.writerow("Delhi") Writes D,e,l,h,i — five fields, one per character. Use writerow(["Delhi"]) instead.
Remember
  • Two separate objects: open() gives the file object, csv.writer(fileobject) gives the writer. The writer never opens or closes the file — you do.
  • writerow() writes exactly one record from one list or tuple, converting numbers to text automatically.
  • writerow() returns what the file object's write() returned — for a text file, the number of characters written (19 for a 17-character row plus its \r\n), not None.
  • Mode 'w' empties the file the moment open() runs; mode 'a' appends and creates the file if missing. Use 'a' for every add-a-record function.
  • writerow("Delhi") writes five fields D,e,l,h,i because a string is a sequence of characters — always pass a list, writerow(["Delhi"]).

The newline='' Rule

Quick answer Opening a CSV for writing without newline='' makes Windows translate the writer's own line ending into an extra carriage return, producing a blank line between every record and doubling the row count that reader() returns.

This is the single most-tested detail in the chapter, and the only one that produces a wrong file without producing an error. Read this section until it is automatic.

Here is the same data written twice — once with newline="" and once without — and then inspected at the byte level, by file size, and by row count.

import csv, os

data = [["Roll", "Name", "Marks"],
        [1, "Aarav", 88],
        [2, "Diya", 92]]

f = open("bad.csv", "w")                  # newline='' MISSING
csv.writer(f).writerows(data)
f.close()

f = open("good.csv", "w", newline="")     # newline='' PRESENT
csv.writer(f).writerows(data)
f.close()

print("bad.csv  bytes:", open("bad.csv", "rb").read())
print("good.csv bytes:", open("good.csv", "rb").read())
print("bad.csv  size :", os.path.getsize("bad.csv"), "bytes")
print("good.csv size :", os.path.getsize("good.csv"), "bytes")

b = list(csv.reader(open("bad.csv", newline="")))
g = list(csv.reader(open("good.csv", newline="")))
print("bad.csv  ->", len(b), "rows:", b)
print("good.csv ->", len(g), "rows:", g)

Real output, run on Windows with Python 3.13:

bad.csv  bytes: b'Roll,Name,Marks\r\r\n1,Aarav,88\r\r\n2,Diya,92\r\r\n'
good.csv bytes: b'Roll,Name,Marks\r\n1,Aarav,88\r\n2,Diya,92\r\n'
bad.csv  size : 43 bytes
good.csv size : 40 bytes
bad.csv  -> 6 rows: [['Roll', 'Name', 'Marks'], [], ['1', 'Aarav', '88'], [], ['2', 'Diya', '92'], []]
good.csv -> 3 rows: [['Roll', 'Name', 'Marks'], ['1', 'Aarav', '88'], ['2', 'Diya', '92']]

There is no room for argument here. The bad file ends every record with \r\r\n — three characters. The good file ends every record with \r\n — two. Three records, one extra byte each, and the size goes from 40 to 43. When you read it back, csv.reader() returns 6 rows instead of 3, with an empty list [] after every real record. Open the bad file in Excel and you see a blank line between every row.

Why it happens. Two separate things each add a carriage return:

  1. csv.writer always ends a record with \r\n. That is what the CSV standard specifies, on every operating system.
  2. A file opened in text mode on Windows translates every \n it is given into \r\n on the way to the disk.

So the writer hands over \r\n; the \r passes through untouched, and the \n is expanded into \r\n. Result: \r + \r\n = \r\r\n. Passing newline="" tells Python to perform no line-ending translation at all, so the writer's \r\n reaches the disk exactly as written.

The rule, in one line: whenever a file object is going to be handed to csv.writer() or csv.reader(), open it with newline="". On write it is mandatory; on read the Python documentation asks for it too, so that a newline stored inside a quoted field is not mangled.

The damage is done at write time and cannot be undone at read time. Here is the same broken file read both ways:

import csv
print("read WITHOUT newline='':", list(csv.reader(open("bad.csv", "r"))))
print("read WITH    newline='':", list(csv.reader(open("bad.csv", "r", newline=""))))

print("Filtering blank rows with  if row:")
f = open("bad.csv", "r", newline="")
for row in csv.reader(f):
    if row:
        print(row)
f.close()

Real output:

read WITHOUT newline='': [['Roll', 'Name', 'Marks'], [], ['1', 'Aarav', '88'], [], ['2', 'Diya', '92'], []]
read WITH    newline='': [['Roll', 'Name', 'Marks'], [], ['1', 'Aarav', '88'], [], ['2', 'Diya', '92'], []]
Filtering blank rows with  if row:
['Roll', 'Name', 'Marks']
['1', 'Aarav', '88']
['2', 'Diya', '92']

Both reads give the same six rows — adding newline="" at read time repairs nothing, because the extra bytes are physically on the disk. The only cure at read time is to skip the empties with if row:, which is worth knowing because sooner or later you will be handed a CSV that somebody else wrote badly. But in your own code, fix it at the source.

One more place the doubling shows up: your screen. Run print(open("good.csv", newline="").read()) on a perfectly good file and you still see a blank line between every row. Nothing is wrong with the file — newline="" passed the real \r\n straight through to print, and the Windows console then expanded that \n exactly the way the file did earlier. It is a display artefact only. So: use newline="" for the file object you hand to csv.reader() or csv.writer(), and a plain print(open("good.csv").read()) when you just want to look at the contents. Every display read in this chapter is written that way.

One practical consequence for exam questions: on a file written without newline="", a record count computed as len(list(csv.reader(f))) comes out at exactly twice the true number of records. If your counting program reports double, this is why.

Correct open for writing open("data.csv", "w", newline="") The one rule of this chapter. Prevents the blank line between every record on Windows.
Correct open for reading open("data.csv", "r", newline="") Recommended by the docs so a newline stored inside a quoted field is preserved correctly.
Inspect the real bytes open("data.csv", "rb").read() Shows b'...\r\r\n' when broken and b'...\r\n' when correct. Proof, not guesswork.
Check the file size os.path.getsize("data.csv") Returns bytes as an int. A broken file is larger by exactly one byte per record.
Skip blank rows on read if row: Placed inside the read loop. An empty row is the empty list []. The fix for a file someone else wrote badly.
Symptom to recognise len(list(csv.reader(f))) Returns exactly 2n on a file written without newline='' — n records plus n blank rows.
Remember
  • Without newline='' every record ends with \r\r\n instead of \r\n — proved by raw bytes, by file size (43 vs 40) and by row count (6 vs 3).
  • The cause is double translation: csv.writer always emits \r\n, and Windows text mode then expands that \n into another \r\n.
  • The rule: pass newline='' to open() for every file you hand to csv.writer() or csv.reader(). Mandatory on write, recommended on read.
  • The corruption happens at write time and cannot be undone by reading differently — reading with or without newline='' gives the identical six broken rows.
  • For a file already damaged, skip the empty rows with if row: inside the read loop. A record count on such a file comes out exactly double.
  • print(open(f, newline='').read()) shows a blank line between rows even for a good file — that is the console doing the same expansion, not a damaged file. Use a plain open() for display reads.

Bulk Writing with writerows() and How Quoting Works

Quick answer writerows() takes a list of sequences and writes them all in one call, returning None, while the writer quotes any field containing the delimiter, a quote character or a newline — and doubles any internal quote — so the data survives a round trip unchanged.

writerow() writes one record. writerows() writes many: it takes a list of sequences — a list of lists, or a list of tuples — and writes each one as a line. It is exactly equivalent to a loop calling writerow(), just shorter.

Watch the s. writerow(row) takes one row. writerows(rows) takes a list of rows. Passing a flat list of numbers such as [1, 2, 3] to writerows() fails outright with Error: iterable expected, not int, because each element has to be a whole record. A flat list of strings is worse, because it does not fail: writerows(["ab", "cd"]) writes a,b and then c,d, one character per field.

This example also shows exactly when the writer decides to add quotes. Notice the shop name containing a comma, and the one containing double quotes.

import csv

items = [["Item", "Shop", "Price"],
         ["Notebook", "Sharma Stationers, Pune", 45],
         ["Geometry Box", 'Verma "Value" Mart', 120]]

f = open("items.csv", "w", newline="")
wr = csv.writer(f)
print("writerows() returned:", wr.writerows(items))
f.close()

print("--- file on disk ---")
print(open("items.csv").read())
print("--- rows back ---")
for r in csv.reader(open("items.csv", newline="")):
    print(r)

# a different separator
f = open("pipe.csv", "w", newline="")
csv.writer(f, delimiter="|").writerows(items)
f.close()
print("--- pipe.csv ---")
print(open("pipe.csv").read())
print("read with default comma:", list(csv.reader(open("pipe.csv", newline="")))[1])
print("read with delimiter='|':", list(csv.reader(open("pipe.csv", newline=""), delimiter="|"))[1])

Real output:

writerows() returned: None
--- file on disk ---
Item,Shop,Price
Notebook,"Sharma Stationers, Pune",45
Geometry Box,"Verma ""Value"" Mart",120

--- rows back ---
['Item', 'Shop', 'Price']
['Notebook', 'Sharma Stationers, Pune', '45']
['Geometry Box', 'Verma "Value" Mart', '120']
--- pipe.csv ---
Item|Shop|Price
Notebook|Sharma Stationers, Pune|45
Geometry Box|"Verma ""Value"" Mart"|120

read with default comma: ['Notebook|Sharma Stationers', ' Pune|45']
read with delimiter='|': ['Notebook', 'Sharma Stationers, Pune', '45']

writerows() returned None. Compare this with writerow(), which returned 19 in Section 2. A question asking you to distinguish the two methods wants both facts: one row versus many rows, and a character count versus None.

Quoting is decided per field, not per row. The default policy is csv.QUOTE_MINIMAL: quote a field only when it must be quoted. Look at line 2 of the file — Notebook and 45 are bare, but "Sharma Stationers, Pune" is quoted, because it contains the delimiter. A field is quoted when it contains the delimiter, the quote character, or a newline; otherwise it is written plain.

An internal quote is doubled, not backslashed. Verma "Value" Mart was stored as "Verma ""Value"" Mart" — the whole field wrapped in quotes, and each inner " written twice. That is the CSV convention, and it is a common trap for anyone parsing CSV by hand. The reader undid it perfectly: the field came back as Verma "Value" Mart, exactly what went in.

The delimiter is a shared secret. The pipe-separated file demonstrates why. When written with delimiter="|", the comma inside Sharma Stationers, Pune is no longer special, so that field was not quoted. But when the file is read back with the default comma delimiter, the reader splits on the wrong character and returns garbage: ['Notebook|Sharma Stationers', ' Pune|45'] — two fields, split at the one comma that happened to be in the data. The header row of the same file, which has no comma at all, comes back as a single field: ['Item|Shop|Price']. Tell the reader the same delimiter and the row is perfect again. Whatever delimiter and quotechar you write with, you must read with.

Two more conversions worth knowing, both observed by running writerow([1, 2.5, None, True, "hi"]) and reading it back:

'1,2.5,,True,hi\r\n'
['1', '2.5', '', 'True', 'hi']

None becomes an empty field — not the text None — so it reads back as ''. But True becomes the text 'True'. And every single field, including the number 1, comes back as a string. That last point is the whole of the next section.

Write many records wr.writerows([[1, "A"], [2, "B"]]) Takes a list of lists or tuples. Returns None — never print its return value expecting a count.
Header then data wr.writerow(hdr); wr.writerows(data) The standard pattern: one header row, then every data row in a single call.
Change the separator csv.writer(f, delimiter="|") Writes pipe-separated values. reader() must be given delimiter="|" too, or the record is split on the wrong character — a row with no comma in it comes back as one long single field.
Quote every field csv.writer(f, quoting=csv.QUOTE_ALL) Writes "Aarav","88","Delhi". Fields still read back as plain strings without the quotes.
Default quoting policy csv.QUOTE_MINIMAL The default. Quotes only fields containing the delimiter, the quotechar, or a newline.
Change the quote character csv.writer(f, quotechar="'") Default is the double quote. An internal occurrence of the quotechar is doubled, not backslashed.
Remember
  • writerows(list_of_lists) writes many records in one call and returns None; writerow(one_list) writes one record and returns a character count.
  • The default quoting policy QUOTE_MINIMAL quotes a field only when it contains the delimiter, a quote character, or a newline — decided per field, not per row.
  • An internal double quote is escaped by doubling it: Verma "Value" Mart is stored as "Verma ""Value"" Mart". reader() reverses this exactly.
  • Whatever delimiter or quotechar you write with must be passed to reader() as well, or the row comes back split on the wrong character — or not split at all.
  • None is written as an empty field and reads back as '' (not 'None'), while True is written as the text True.
  • writerows() needs a list of records: writerows([1, 2, 3]) raises Error: iterable expected, not int, and writerows(["ab", "cd"]) silently writes a,b and c,d.

Reading Back with reader() — Everything Is a String

Quick answer csv.reader(fileobject) returns a one-pass iterator that yields each record as a list of strings, so a mark written as the integer 88 comes back as '88' and every calculation needs an explicit int() or float() conversion.

csv.reader(fileobject) gives you a reader object. It is not a list. It is an iterator — you walk it with a for loop, and each turn of the loop hands you one record as a list of strings.

Those two words, list of strings, are where most marks are lost. Write the integer 88 into a CSV and you get back the string '88'. The file has no idea what a number is — it stores characters. This program writes a class marks file, reads it back, shows the type, then demonstrates the bug and the fix.

import csv

# make the file first
rows = [["Roll", "Name", "Marks"],
        [1, "Aarav Sharma", 88],
        [2, "Diya Nair", 92],
        [3, "Kabir Menon", 76],
        [4, "Ishita Rao", 61]]
with open("marks.csv", "w", newline="") as f:
    csv.writer(f).writerows(rows)

# --- what reader() actually hands you ---
with open("marks.csv", "r", newline="") as f:
    rd = csv.reader(f)
    header = next(rd)
    first = next(rd)
print("written  :", rows[1])
print("read back:", first)
print("Marks field type:", type(first[2]), "value:", repr(first[2]))

# --- the classic mistake ---
with open("marks.csv", "r", newline="") as f:
    rd = csv.reader(f)
    next(rd)
    total = 0
    try:
        for row in rd:
            total = total + row[2]
    except TypeError as e:
        print("TypeError:", e)

# --- the fix: int() ---
with open("marks.csv", "r", newline="") as f:
    rd = csv.reader(f)
    next(rd)
    total = count = 0
    topper = ""
    best = -1
    for roll, name, m in rd:
        m = int(m)
        total += m
        count += 1
        if m > best:
            best, topper = m, name
print("Total   =", total)
print("Count   =", count)
print("Average =", round(total / count, 2))
print("Topper  =", topper, "with", best)

Real output:

written  : [1, 'Aarav Sharma', 88]
read back: ['1', 'Aarav Sharma', '88']
Marks field type:  value: '88'
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Total   = 317
Count   = 4
Average = 79.25
Topper  = Diya Nair with 92

The round trip is not type-preserving. [1, 'Aarav Sharma', 88] went in; ['1', 'Aarav Sharma', '88'] came out. The roll number and the marks are now text. type(first[2]) confirms str, and repr() shows the quotes.

The TypeError is the lucky case. Adding row[2] to an integer total raised unsupported operand type(s) for +: 'int' and 'str' — Python stopped you. But if you had initialised total = "" instead of 0, Python would have happily concatenated the marks into '88927661' and printed a nonsense total with no error at all. This is why int() is not optional. The rule: the moment a numeric field leaves reader(), convert it. Use int() for whole numbers like marks and roll numbers, float() for prices and percentages.

next(rd) skips the header. Call it once before the loop; it consumes the first record and returns it, so the loop starts at real data. Forgetting this is why a program crashes with ValueError: invalid literal for int() with base 10: 'Marks' — it tried to convert the header text.

Notice also the loop header for roll, name, m in rd:. Because every record is a three-element list, you can unpack it straight into three names instead of writing row[0], row[1], row[2]. This only works if every row has exactly three fields.

A reader is a one-pass iterator. Once you have walked it, it is empty. This trips up programs that try to count records and then loop over them again:

import csv
f = open("marks.csv", "r", newline="")
rd = csv.reader(f)
print("pass 1 count:", len(list(rd)))
print("pass 2 count:", len(list(rd)))
f.close()

with open("marks.csv", "r", newline="") as f:
    rd = csv.reader(f)
    next(rd)
    print("Students scoring above 80:")
    for roll, name, m in rd:
        if int(m) > 80:
            print(roll, name, m)
print("file closed?", f.closed)

Real output:

pass 1 count: 5
pass 2 count: 0
Students scoring above 80:
1 Aarav Sharma 88
2 Diya Nair 92
file closed? True

The second pass returns 0. If you need the data twice, either store it once with rows = list(rd) and reuse that list, or close and reopen the file. Note too that pass 1 counted 5, not 4 — list(rd) includes the header row, so the number of actual student records is len(list(rd)) - 1. And the with block closed the file for you, confirmed by f.closed printing True.

Create a reader rd = csv.reader(f) Returns a reader object (an iterator over rows), not a list. The file must be open for reading — normally 'r'.
Loop the records for row in rd: Each row is a list of strings, one element per field.
Skip the header next(rd) Consumes and returns the first record. Call once, before the loop, and ignore the value.
Read everything at once rows = list(rd) Loads the whole file into a list of lists and exhausts the reader. Includes the header row.
Count data records len(list(csv.reader(f))) - 1 The -1 removes the header. On a file written without newline='' this figure comes out doubled.
Convert before calculating total = total + int(row[2]) reader() gives '88', not 88. Without int() you get a TypeError or a silent string concatenation.
Remember
  • csv.reader(f) returns a one-pass iterator, not a list. Each record comes out as a list of strings.
  • Nothing survives as a number: the integer 88 written to the file comes back as the string '88'. Convert with int() or float() before any arithmetic.
  • Adding a string field to an integer raises TypeError, but adding it to a string silently concatenates — which is why the conversion must be deliberate, not accidental.
  • next(rd) called once before the loop consumes and skips the header row; forgetting it causes ValueError: invalid literal for int().
  • Walking a reader a second time yields nothing. Use rows = list(rd) if you need the data more than once, and remember list(rd) includes the header, so subtract 1 for the record count.

The formula sheet

Every formula in this chapter, in one place — screenshot it before your exam.

import csv
Import the module
f = open("data.csv", "r", newline="")
Open to read
f = open("data.csv", "w", newline="")
Open to create / overwrite
f = open("data.csv", "a", newline="")
Open to append
f.close()
Close the file
with open("data.csv", "w", newline="") as f:
Auto-closing form
wr = csv.writer(f)
Create a writer
wr.writerow(["Roll", "Name", "Marks"])
Write one record
wr.writerow((1, "Aarav", 88))
Tuples work too
open("f.csv", "w", newline="")
Overwrite mode
open("f.csv", "a", newline="")
Append mode
wr.writerow("Delhi")
Gotcha: bare string
open("data.csv", "w", newline="")
Correct open for writing
open("data.csv", "r", newline="")
Correct open for reading
open("data.csv", "rb").read()
Inspect the real bytes
os.path.getsize("data.csv")
Check the file size
if row:
Skip blank rows on read
len(list(csv.reader(f)))
Symptom to recognise
wr.writerows([[1, "A"], [2, "B"]])
Write many records
wr.writerow(hdr); wr.writerows(data)
Header then data
csv.writer(f, delimiter="|")
Change the separator
csv.writer(f, quoting=csv.QUOTE_ALL)
Quote every field
csv.QUOTE_MINIMAL
Default quoting policy
csv.writer(f, quotechar="'")
Change the quote character
rd = csv.reader(f)
Create a reader
for row in rd:
Loop the records
next(rd)
Skip the header
rows = list(rd)
Read everything at once
len(list(csv.reader(f))) - 1
Count data records
total = total + int(row[2])
Convert before calculating

Test yourself

Tap an answer to check it instantly — you'll see why it's right, and what to revise if it isn't.

0 correct · 0/12 answered
Q1

What is printed? import csv f = open('city.csv', 'w', newline='') csv.writer(f).writerow('Delhi') f.close() print(list(csv.reader(open('city.csv', newline='')))[0])

Q2

A CSV file d.csv contains exactly these two lines: A,10 B,20 What is printed? import csv r = list(csv.reader(open('d.csv', newline=''))) print(r[0][1] + r[1][1])

Q3

This program is run on Windows. What is the value of n? import csv f = open('t.csv', 'w') csv.writer(f).writerows([[1, 2], [3, 4], [5, 6]]) f.close() n = len(list(csv.reader(open('t.csv', newline=''))))

Q4

What is printed? import csv f = open('m.csv', 'w', newline='') csv.writer(f).writerow([1, 2.5, None, True, 'hi']) f.close() print(list(csv.reader(open('m.csv', newline='')))[0])

Q5

What is printed? import csv f = open('p.csv', 'w', newline='') w = csv.writer(f) print(w.writerow(['UPI', 250])) f.close()

Q6

The file r.csv already contains the text Roll,Name with no line terminator at the end. What is printed? import csv f = open('r.csv', 'a', newline='') csv.writer(f).writerow([1, 'Aarav']) f.close() print(list(csv.reader(open('r.csv', newline=''))))

Q7

What is printed? import csv f = open('a.csv', 'w', newline='') csv.writer(f).writerow(['Rahul', 'Sector 12, Noida', 500]) f.close() row = list(csv.reader(open('a.csv', newline='')))[0] print(len(row))

Q8

Why must newline='' be passed to open() when a file is going to be used with csv.writer()?

Q9

Which statement about writerow() and writerows() is completely correct?

Q10

A CSV file stores a student's marks as 88. After reading the file with csv.reader(), what does the corresponding field contain?

Q11

A function must add a new employee record to Employee.csv without disturbing the records already stored. Which open() call is correct?

Q12

Why is it wrong to create a CSV file by writing lines built with ",".join(fields) instead of using csv.writer()?

NCERT solutions & previous-year questions

Step-by-step model answers — tap a question to reveal the full solution.

NCERT questions 6

1 Write a Python program to create a CSV file 'student.csv' to store the roll number, name and marks of students, and then read the file and display all the records.Creating and reading a CSV file

Write with csv.writer() and writerows(), then read with csv.reader(). Note newline="" in both open() calls.

import csv

students = [["Roll", "Name", "Marks"],
            [1, "Aarav Sharma", 88],
            [2, "Diya Nair", 92],
            [3, "Kabir Menon", 76]]

with open("student.csv", "w", newline="") as f:
    csv.writer(f).writerows(students)

with open("student.csv", "r", newline="") as f:
    for row in csv.reader(f):
        print(row)

Real output:

['Roll', 'Name', 'Marks']
['1', 'Aarav Sharma', '88']
['2', 'Diya Nair', '92']
['3', 'Kabir Menon', '76']

Observe that the roll numbers and marks were written as integers but display as '1' and '88' — every field returned by reader() is a string. The with block closes each file automatically, which matters here because data still sitting in the write buffer would otherwise not be on disk when the read begins.

2 Write a program to count the number of records present in a CSV file, excluding the header row.Counting records

Skip the header once with next(), then count what remains.

import csv

with open("student.csv", "r", newline="") as f:
    rd = csv.reader(f)
    next(rd)                 # consume the header row
    c = 0
    for row in rd:
        c += 1

print("Number of records =", c)

Real output for the three-student file built in Question 1:

Number of records = 3

Two shortcuts are worth knowing. len(list(rd)) after next(rd) gives the same answer in one line. And len(list(csv.reader(f))) - 1 works without next(), the -1 removing the header.

One warning: if the file was written without newline="", every real record is followed by a blank row and this count comes out exactly double. If your answer is twice what you expect, the fault is in the program that wrote the file, not this one.

3 Write a program that searches student.csv for a given roll number and displays the matching record. If the roll number is not present, display a suitable message.Searching a CSV file

Walk the records and compare. Because reader() returns strings, convert row[0] with int() before comparing it with an integer roll number — otherwise '2' == 2 is False and the search always fails.

import csv

def search(roll):
    with open("student.csv", "r", newline="") as f:
        rd = csv.reader(f)
        next(rd)
        for row in rd:
            if int(row[0]) == roll:
                return row
    return None

print(search(2))
print(search(9))

Real output:

['2', 'Diya Nair', '92']
None

The function returns as soon as it finds a match, so it does not read the rest of the file needlessly. In a full board answer you would print a message instead of returning None, for example print("Record not found"). An equally valid approach is to keep a flag such as found = False, set it inside the loop, and test it after the loop — that pattern is needed when several records may match.

4 What is the purpose of the newline='' argument in open() while writing a CSV file? What happens if it is omitted?The newline='' argument

newline="" tells Python to perform no line-ending translation on data passing through the file object.

It is needed because two things each want to add a carriage return. First, csv.writer always terminates a record with \r\n — that is the CSV standard, on every operating system. Second, a file opened in ordinary text mode on Windows converts every \n it receives into \r\n. So the writer's \r\n becomes \r followed by \r\n, that is \r\r\n.

If it is omitted, a blank line appears between every record. Here is the proof — the same data written both ways, then inspected as raw bytes:

import csv, os
data = [["Roll", "Name", "Marks"], [1, "Aarav", 88], [2, "Diya", 92]]

f = open("bad.csv", "w")
csv.writer(f).writerows(data)
f.close()

f = open("good.csv", "w", newline="")
csv.writer(f).writerows(data)
f.close()

print(open("bad.csv", "rb").read())
print(open("good.csv", "rb").read())
print(os.path.getsize("bad.csv"), os.path.getsize("good.csv"))
print(len(list(csv.reader(open("bad.csv", newline="")))))
print(len(list(csv.reader(open("good.csv", newline="")))))

Real output:

b'Roll,Name,Marks\r\r\n1,Aarav,88\r\r\n2,Diya,92\r\r\n'
b'Roll,Name,Marks\r\n1,Aarav,88\r\n2,Diya,92\r\n'
43 40
6
3

The broken file is 43 bytes against 40 — one extra byte per record — and csv.reader() returns 6 rows instead of 3, with an empty list between each real record. Importantly, no error is ever raised, and the damage cannot be repaired at read time: reading the bad file with newline="" still gives the same six rows, because the extra bytes are physically on the disk.

5 Write a program that reads student.csv and copies only those records where the marks are 80 or above into a new file topper.csv, keeping the header row.Filtering records into a new CSV file

Open both files in a single with statement — one for reading, one for writing. Copy the header first with writerow(), then copy the qualifying records.

import csv

with open("student.csv", newline="") as src, open("topper.csv", "w", newline="") as dst:
    rd = csv.reader(src)
    wr = csv.writer(dst)
    wr.writerow(next(rd))          # copy the header across
    n = 0
    for row in rd:
        if int(row[2]) >= 80:
            wr.writerow(row)
            n += 1

print("Copied", n, "records")
print(open("topper.csv").read())

Real output, run on the three-student student.csv built in Question 1 (marks 88, 92 and 76):

Copied 2 records
Roll,Name,Marks
1,Aarav Sharma,88
2,Diya Nair,92

The essential detail is int(row[2]). Without it you would be comparing the string '76' with the integer 80, which raises TypeError: '>=' not supported between instances of 'str' and 'int'. Comparing '76' >= '80' as strings would be worse still — string comparison is alphabetical, so '9' > '80' is True and a student scoring 9 would be copied as a topper.

Note the final line: print(open("topper.csv").read()) is a plain display read, deliberately without newline="". Add newline="" there and the console shows a blank line between every row, even though the file itself is perfectly correct.

6 Differentiate between writerow() and writerows(). State what each of them returns.writerow() versus writerows()
Pointwriterow()writerows()
ArgumentOne sequence (a list or tuple of field values)A list of sequences — a list of lists or a list of tuples
Records writtenExactly oneOne for every sequence in the list
ReturnsThe value returned by the file object's write() — for a text file, the number of characters writtenNone
Typical useAdding a single record, or writing the headerWriting a whole table already held in a list

Verified by running both:

import csv
f = open("demo.csv", "w", newline="")
w = csv.writer(f)
print("writerow  ->", w.writerow(["UPI", 250]))
print("writerows ->", w.writerows([["NEFT", 900]]))
f.close()

Real output:

writerow  -> 9
writerows -> None

UPI,250 is 7 characters plus the \r\n terminator, hence 9. The two methods are otherwise interchangeable: writerows(data) does exactly what a for row in data: writerow(row) loop would do. In practice the header goes out with writerow() and the body with writerows().

Previous-year board questions 4

Q1 Aman is a Python programmer working in a school. For the annual sports event he has created a CSV file named Result.csv to store the results of students in different sports events. The structure of Result.csv is [St_Id, St_Name, Game_Name, Result], where Result is one of 'WON', 'LOST' or 'TIE'. Write the following user-defined functions to perform the given operations: (i) Accept() — to accept a record and add it to Result.csv; (ii) wonCount() — to count and return the number of students who have won any event. [4 marks] 2023 (board pattern)

The add function must open in 'a' mode, otherwise it destroys the existing records. The counting function opens in 'r', skips the header with next(), and compares field index 3.

import csv

def Accept(rec):
    f = open("Result.csv", "a", newline="")
    csv.writer(f).writerow(rec)
    f.close()

def wonCount():
    f = open("Result.csv", "r", newline="")
    rd = csv.reader(f)
    next(rd)
    c = 0
    for row in rd:
        if row[3] == "WON":
            c += 1
    f.close()
    return c

# create the file with its header, then add records
f = open("Result.csv", "w", newline="")
csv.writer(f).writerow(["St_Id", "St_Name", "Game_Name", "Result"])
f.close()

Accept([1, "Aarav Sharma", "Kabaddi", "WON"])
Accept([2, "Diya Nair", "Chess", "LOST"])
Accept([3, "Kabir Menon", "Kho-Kho", "WON"])
Accept([4, "Ishita Rao", "Badminton", "TIE"])

print(open("Result.csv").read())
print("Students who WON =", wonCount())

Real output:

St_Id,St_Name,Game_Name,Result
1,Aarav Sharma,Kabaddi,WON
2,Diya Nair,Chess,LOST
3,Kabir Menon,Kho-Kho,WON
4,Ishita Rao,Badminton,TIE

Students who WON = 2

Where marks are lost in this question. Using 'w' inside Accept() — that empties the file on every call, so the last record is the only one left. Forgetting next(rd) in wonCount() — here it happens to be harmless because the header's fourth field is 'Result', not 'WON', but the same slip is fatal in any question that converts a field with int(). And omitting newline="", which inserts a blank row between records. Note that no comparison in wonCount() needs int(), because 'WON' is genuinely text.

Q2 A CSV file 'Result.csv' contains the records of students. Write a user-defined function COUNTR() to count and display the total number of records present in the file, excluding the header row. [2 marks] 2024 (board pattern)

A short two-mark answer. One mark for correctly opening and reading with csv.reader(), one for the counting logic that excludes the header.

import csv

def COUNTR():
    f = open("Result.csv", "r", newline="")
    rd = csv.reader(f)
    next(rd)                 # skip the header row
    n = 0
    for row in rd:
        n += 1
    f.close()
    print("Total records =", n)

COUNTR()

Real output on the four-student Result.csv from the previous question:

Total records = 4

An equally acceptable one-line body is print("Total records =", len(list(rd))) placed after the next(rd). Read the question wording carefully: if it does not say "excluding the header", drop the next(rd) and the answer becomes 5. Examiners award the mark for the logic matching what was asked.

Q3 A CSV file 'Employee.csv' stores records in the form [Eid, Name, Dept, Salary]. Write a Python program with the following user-defined functions: (i) AddEmp() — to take employee details as parameters and add one record to Employee.csv; (ii) ShowHigh() — to read the file and display the details of all employees whose salary is more than 50000. If no such employee exists, display an appropriate message. [5 marks] SQP 2024-25 (pattern)

The critical difference from the sports question is that the condition here is numeric. reader() hands back '78000' as a string, so the comparison needs int().

import csv

def AddEmp(eid, name, dept, sal):
    f = open("Employee.csv", "a", newline="")
    csv.writer(f).writerow([eid, name, dept, sal])
    f.close()

def ShowHigh():
    f = open("Employee.csv", "r", newline="")
    rd = csv.reader(f)
    next(rd)
    found = 0
    for row in rd:
        if int(row[3]) > 50000:
            print(row[0], row[1], row[2], row[3])
            found += 1
    f.close()
    if found == 0:
        print("No such employee")

f = open("Employee.csv", "w", newline="")
csv.writer(f).writerow(["Eid", "Name", "Dept", "Salary"])
f.close()

AddEmp(101, "Rohan Gupta", "Sales", 42000)
AddEmp(102, "Meera Iyer", "IT", 78000)
AddEmp(103, "Sanjay Patil", "IT", 55000)
AddEmp(104, "Anita Desai", "HR", 39000)

print(open("Employee.csv").read())
print("Employees earning above 50000:")
ShowHigh()

Real output:

Eid,Name,Dept,Salary
101,Rohan Gupta,Sales,42000
102,Meera Iyer,IT,78000
103,Sanjay Patil,IT,55000
104,Anita Desai,HR,39000

Employees earning above 50000:
102 Meera Iyer IT 78000
103 Sanjay Patil IT 55000

Two marks hinge on details. Writing if row[3] > 50000: without int() raises TypeError: '>' not supported between instances of 'str' and 'int' and the whole function fails. And the found counter is not decoration: the question explicitly asks for a message when nothing matches, so a solution without it loses a mark even though the main logic is right.

Q4 (a) Give one point of difference between a CSV file and a text file. (b) A CSV file 'Employee.csv' stores records in the form [Eid, Name, Dept, Salary]. Write a user-defined function DeptTotal() that reads the file and displays the total salary paid in each department. [1 + 3 marks] 2020 (board pattern)

(a) A CSV file stores data in a fixed tabular structure — one record per line with fields separated by a delimiter, usually a comma — so it can be opened directly as a spreadsheet in Excel and read field-by-field with csv.reader(). A plain text file has no such structure; it is free-form text, and any splitting into fields must be programmed by hand with split(). Both are human-readable and both use the same text modes 'r', 'w' and 'a', which is why the structure is the distinguishing point, not readability.

(b) Use a dictionary keyed on the department, adding each salary to the running total. get() supplies a default of 0 for a department seen for the first time.

import csv

def DeptTotal():
    d = {}
    f = open("Employee.csv", "r", newline="")
    rd = csv.reader(f)
    next(rd)
    for row in rd:
        d[row[2]] = d.get(row[2], 0) + int(row[3])
    f.close()
    for k, v in d.items():
        print(k, ":", v)

DeptTotal()

Real output for the four employees Rohan (Sales 42000), Meera (IT 78000), Sanjay (IT 55000) and Anita (HR 39000):

Sales : 42000
IT : 133000
HR : 39000

IT correctly shows 133000, the sum of 78000 and 55000. This is where the string trap bites hardest: drop the int() and d.get(row[2], 0) would attempt 0 + '78000' and raise TypeError. If instead you had used '' as the default, Python would silently concatenate and report the IT total as '7800055000' with no error at all. Always convert a numeric CSV field the moment it leaves reader().

Part of Priodemy for School

Interactive CBSE lessons, Class 8–12 — free with every school on Priodemy EduSuite. Explore more chapters and labs on the Priodemy for School hub.

Ask AI