Persisting and Sharing Data

Tabular Data with CSV


Learning Objectives

  • You can connect CSV rows and columns with dictionary records.
  • You can use DictReader and DictWriter to read and write quoted CSV fields.
  • You can convert parsed text into the types the application needs.
  • You can test a CSV round trip, including quoted fields and an empty table.

A table organizes information into rows and columns. CSV is a text format commonly used to exchange that kind of data. Each data row can become a record like the dictionaries from Part 4.

Rows, Columns, and Headers

The examples use this small table, saved as practice_sessions.csv:

subject,minutes
Programming,25
Math,40
"Reading, fiction",15

The first row contains column names. Each later row contains one session.

The quotation marks around Reading, fiction matter. The comma is part of the subject, not the boundary between two fields. A plain .split(",") would divide that subject incorrectly.

The CSV library handles quoting and separators. Use .split() for controlled delimiter-separated text when its contract permits it, but do not treat that operation as a complete CSV parser.

Read Rows as Dictionaries

Predict the three subject names, then run this complete example. It creates and reads its file during the same execution. Change the last print to print(row) to inspect each dictionary, then to print(type(row["minutes"])) to investigate the value type.

No files opened Select a file to edit

csv.DictReader uses the header names as dictionary keys. Iterating over the reader supplies one dictionary for each data row.

reader.fieldnames gives the list of column names read from the header. A header-only file has these names but produces no data rows.

The output is:

Programming
Math
Reading, fiction

newline="" lets the CSV library handle line endings itself. Use it when opening CSV files for both reading and writing.

The reader needs the open file while it is producing rows. Complete the loop inside the with block. The records you construct during that loop can be returned after the file closes. list(reader) is another way to collect all remaining row dictionaries into a list; that call must also happen while the file is open.

Text Fields Still Need Conversion

With the options used here, CSV field values are strings. The minutes column does not become numeric merely because its contents look like numbers:

In the editor above, replace the print inside the loop with these two lines, keeping both indented inside the loop:

minutes = int(row["minutes"])
print(f'{row["subject"]}: {minutes + 5} minutes after adding a break')

This example assumes the expected headers and valid integer text. Chapter 7 will add checks for those assumptions. For now, concentrate on reading the fields and converting minutes. Adding 5 works because minutes is an integer; the original field in row is still text. The next programming exercise asks you to collect typed records for another function to use.

Boolean fields also need an agreed textual representation. For example, a catalogue might use "yes" and "no". bool("no") produces True because the string is nonempty; it does not interpret the word’s meaning. First check that the text is one of the accepted spellings, then compare it with "yes" to produce the boolean value.

Now collect the parsed rows into typed records. Use the trace above to decide where conversion belongs.

Read session rows

0 / 15 points

Write read_session_csv(path) using the CSV library. Files are UTF-8 with the exact header subject,minutes. Each row has a subject string and valid positive integer text. Return a list of dictionaries such as {"subject": "Math", "minutes": 20}, with subjects preserved exactly and minutes converted to integers, in row order. Use newline="" when opening the file to preserve line breaks inside quoted fields. Repeated subjects are separate sessions. A header-only file returns []. Do not print or modify the file. Let file errors propagate. Header and invalid-row validation will be added in a later exercise.

Work locally

Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.

FileRole
main.pyWrite read_session_csv(path) here.
check.pySupplied assertions about loaded records and integer minutes; extend them.
sessions.csvSample input with two sessions, including a quoted comma.

Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:

SystemRun the checks
Windowspy check.py
macOS/Linuxpython3 check.py

Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.

The supplied check expects two records from sessions.csv: "Reading, fiction" with integer minutes 15, and "Math" with integer minutes 20. Create a separate CSV containing only the header and check that it loads as [].

main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.

Submit your solution

Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:

SystemCommand
Windowspy -m zipfile -c submission.zip main.py check.py sessions.csv
macOS/Linuxpython3 -m zipfile -c submission.zip main.py check.py sessions.csv

Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.

Write Records as Rows

A writer needs the field names and their order. Predict the types after this complete round trip, then run it. The integer becomes CSV text even though its value is preserved.

No files opened Select a file to edit

The writer creates the quoting required for the comma in the subject. There is no need to write those quotation marks yourself.

fieldnames is a keyword argument specifying the column order. writeheader() writes the names once. writerow(...) writes one record. The integer minutes are represented as text in the output.

To export a collection, keep the header outside the loop and write each record inside it. Writing only the header creates a valid empty table with a known shape, different from a completely empty file whose columns are unspecified.

Now extend writing from one record to a collection. Also check the empty collection, which should produce only the header.

Export session rows

0 / 15 points

Write write_session_csv(path, sessions) using the CSV library. sessions is a list of dictionaries with exactly two keys: subject (a string) and minutes (a positive integer), for example {"subject": "Math", "minutes": 20}. Write a UTF-8 CSV file with the header subject,minutes, followed by the records in order. Preserve commas, quotation marks, newlines within subjects, and repeated subjects. Replace previous contents. An empty list writes the header only. Return None, without printing or changing the records. Use newline="" when opening the CSV file. Correct CSV quoting is required; cosmetic line-ending differences are accepted.

Work locally

Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.

FileRole
main.pyWrite write_session_csv(path, sessions) here.
check.pySupplied checks that write CSV and read it back; extend them.

Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:

SystemRun the checks
Windowspy check.py
macOS/Linuxpython3 check.py

Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.

The check writes practice_export.csv for {"subject": "Reading, fiction", "minutes": 15} and reads it back with csv.DictReader. Open the generated CSV in the IDE to inspect how the comma is quoted. Add a check that an empty session list writes only the header.

main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.

Submit your solution

Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:

SystemCommand
Windowspy -m zipfile -c submission.zip main.py check.py
macOS/Linuxpython3 -m zipfile -c submission.zip main.py check.py

Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.

Reading rows can also feed the grouped-total pattern from Part 4. In the next exercise, decide which field supplies the grouping key and which value contributes to its total.

Summarize station readings

0 / 20 points

Write reading_totals(path). Use the CSV library and newline="" when opening the file. A UTF-8 CSV file has exact header station,reading. Every row has a station string and valid integer text, which may be zero or negative. Return a dictionary mapping each occurring station to the total of all its readings. Preserve station text exactly, and combine repeated stations even when their rows are separated. A header-only file returns {}. Do not print or modify the file.

For rows north,5, south,-2, and north,-1, return {"north":4,"south":-2}.

Work locally

Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.

FileRole
main.pyWrite reading_totals(path) here.
check.pySupplied assertions about totals for each station; extend them.
readings.csvSample input with repeated stations and negative readings.

Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:

SystemRun the checks
Windowspy check.py
macOS/Linuxpython3 check.py

Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.

The supplied check calls reading_totals("readings.csv") and expects {"north": 4, "south": -2}. Add a separate header-only CSV and check that its result is {}.

main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.

Submit your solution

Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:

SystemCommand
Windowspy -m zipfile -c submission.zip main.py check.py readings.csv
macOS/Linuxpython3 -m zipfile -c submission.zip main.py check.py readings.csv

Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.

Keep Earlier Records When Writing New Ones

Mode "w" replaces a file’s contents. A practice history needs a different operation: add the next attempt while retaining earlier ones. Mode "a" opens a file for appending, creating it if it does not exist.

This complete example first prepares a fresh practice file with its header. It then appends two attempts separately. Predict how many data rows the reader will find.

No files opened Select a file to edit

The setup uses "w" deliberately so rerunning this example starts the experiment afresh. A real history loader must retain the existing file. Write the header only when creating that history, then append data rows.

Change the append mode inside the loop to "w". Predict what will be lost before running it, then restore "a". The final assertion may fail because repeated replacement has destroyed the earlier rows and header.

Use a CSV writer for responses too: a learner may type a comma or quotation mark. The correctness flag is text when read from CSV. Validate "0" or "1" and convert it deliberately; bool("0") would still be True.

Headers and Row Shape Are Part of the Contract

What happens when a header is misspelled, a row has too many fields, or a numeric field is empty? A successful CSV parse does not establish that the table is suitable for your application.

DictReader.fieldnames exposes the header names. A shorter row can yield a missing value represented by None. An extra field can appear under the key None with the default reader settings. These are signals to validate, not values to silently calculate with.

Chapter 7 uses these observations to distinguish valid tables from invalid application data. For now, the important boundary is visible:

CSV characters → parsed row of strings → typed application record

Check Your Understanding

  1. Why is .split(",") not sufficient for arbitrary CSV fields?
  2. What supplies the dictionary keys when using DictReader?
  3. Why does a minutes field need conversion?
  4. How does a header-only file differ from a file with no contents?