{"id":"writing-files-csv","slug":"csv","title":"Writing CSV files","text":"A CSV file is written like any other text file, one line at a time. Each line is the values of one record with the separator between them, and a line break at the end. An f-string builds such a line neatly. Here the data comes from a dictionary, with a header line first:\n\n```python\nstock = {\"pens\": 120, \"notebooks\": 45, \"staplers\": 7}\n\nwith open(\"stock.csv\", \"w\") as file:\n    file.write(\"item;amount\\n\")\n    for item, amount in stock.items():\n        file.write(f\"{item};{amount}\\n\")\n\nwith open(\"stock.csv\") as file:\n    print(file.read())\n```\n\n```output\nitem;amount\npens;120\nnotebooks;45\nstaplers;7\n```\n\nThe f-string turns the numbers into text on the way, so `write` gets the string it needs.\n\n### Lines with many values\n\nSome records hold a whole list of values, such as a day's temperature readings, and the lists can be of different lengths. An f-string needs a `{...}` for every value, so it can't write a list whose length it doesn't know. The string method `join`, from the module on strings and lists, can: it puts the separator between the strings of a list, and never after the last one. `join` only accepts strings, so the loop below turns each number into one with `str` first:\n\n```python\nreadings = {\n    \"Mon\": [12, 15, 9, 11],\n    \"Tue\": [14, 16],\n}\n\nwith open(\"readings.csv\", \"w\") as file:\n    for day, values in readings.items():\n        fields = [day]\n        for value in values:\n            fields.append(str(value))\n        file.write(\";\".join(fields) + \"\\n\")\n\nwith open(\"readings.csv\") as file:\n    print(file.read())\n```\n\n```output\nMon;12;15;9;11\nTue;14;16\n```\n\nThe list `fields` starts with the day, and every reading is added after it as a string. Leave out the `str`, and `join` stops at the first number with `TypeError: sequence item 1: expected str instance, int found`.\n\n### Read, process, write\n\nMany programs read data from one file, work something out and write the results to another file. Such a program is easiest to write, and to fix, in pieces: a function for each part. Here a shop keeps its prices in `prices.csv`, and for a sale it wants a second file with the old and the new price of every item:\n\n```file prices.csv\nitem;price\ncoffee;3.20\nbagel;2.50\njuice;4.00\n```\n\n```python\ndef read_prices(filename):\n    prices = {}\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"item\":\n                continue\n            prices[parts[0]] = float(parts[1])\n    return prices\n\n\ndef sale_lines(prices, percent):\n    lines = [\"item;price;sale price\"]\n    for item, price in prices.items():\n        sale = price * (100 - percent) / 100\n        lines.append(f\"{item};{price:.2f};{sale:.2f}\")\n    return lines\n\n\ndef write_lines(filename, lines):\n    with open(filename, \"w\") as file:\n        for line in lines:\n            file.write(line + \"\\n\")\n\n\nprices = read_prices(\"prices.csv\")\nwrite_lines(\"sale.csv\", sale_lines(prices, 20))\n\nwith open(\"sale.csv\") as file:\n    print(file.read())\n```\n\n```output\nitem;price;sale price\ncoffee;3.20;2.56\nbagel;2.50;2.00\njuice;4.00;3.20\n```\n\nLook at what each function needs to know. `read_prices` is the only one that knows what the input file looks like, header line and all. `sale_lines` never touches a file: it gets a dictionary and returns a list of strings, so you can try it out with `print(sale_lines({\"tea\": 2.0}, 50))` before any file exists. And `write_lines` knows nothing about prices: it writes any list of lines into any file, so your next program can use it just as it is. For a sale of 30 percent, only the number in the call changes.\n","task":"Fill in the blanks so the program writes the scores in the dictionary to `scores.csv`, one `name;score` line per player. The file should hold:\n\n```text\nAda;31\nBen;27\nCleo;35\n```\n","kind":"fill","seed":"scores = {\"Ada\": 31, \"Ben\": 27, \"Cleo\": 35}\nwith open(\"scores.csv\", [[\"w\"‖'w']]) as file:\n    for name, score in scores.items():\n        file.write(f\"{name};{score}[[\\n]]\")\n","solution":"scores = {\"Ada\": 31, \"Ben\": 27, \"Cleo\": 35}\nwith open(\"scores.csv\", \"w\") as file:\n    for name, score in scores.items():\n        file.write(f\"{name};{score}\\n\")\n","hints":["The file is created from scratch, so it's opened for writing.","Each line must end with a line break, which is written `\\n` inside the string."],"tests":[{"name":"Files","stdin":"","writes":{"scores.csv":"Ada;31\nBen;27\nCleo;35\n"},"expect":"","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Write a function `save_inventory(filename, inventory)`. The parameter `inventory` is a dictionary from item names to amounts. The function writes the file with a header line `item;count` and then one `item;count` line per item, in the dictionary's order.\n\nFor example, `save_inventory(\"inventory.csv\", {\"bolts\": 250, \"nuts\": 310, \"washers\": 75})` writes this file:\n\n```text\nitem;count\nbolts;250\nnuts;310\nwashers;75\n```\n","kind":"code","seed":"def save_inventory(filename, inventory):\n    pass\n\n\nif __name__ == \"__main__\":\n    save_inventory(\"inventory.csv\", {\"bolts\": 250, \"nuts\": 310, \"washers\": 75})\n","solution":"def save_inventory(filename, inventory):\n    with open(filename, \"w\") as file:\n        file.write(\"item;count\\n\")\n        for item, count in inventory.items():\n            file.write(f\"{item};{count}\\n\")\n\n\nif __name__ == \"__main__\":\n    save_inventory(\"inventory.csv\", {\"bolts\": 250, \"nuts\": 310, \"washers\": 75})\n","hints":["Open the file named by the parameter `filename` with the mode `\"w\"`, and write the header line first.","Go through `inventory.items()` and write each pair as an f-string: `f\"{item};{count}\\n\"`."],"tests":[{"name":"save_inventory(\"inventory.csv\", {\"bolts\": 250, \"nuts\": 310, \"washers\": 75})","stdin":"","call":"save_inventory(\"inventory.csv\", {\"bolts\": 250, \"nuts\": 310, \"washers\": 75})\n","writes":{"inventory.csv":"item;count\nbolts;250\nnuts;310\nwashers;75\n"},"expect":"","hidden":false},{"name":"save_inventory(\"kitchen.csv\", {\"plates\": 12, \"cups\": 9})","stdin":"","call":"save_inventory(\"kitchen.csv\", {\"plates\": 12, \"cups\": 9})\n","writes":{"kitchen.csv":"item;count\nplates;12\ncups;9\n"},"expect":"","hidden":true}],"require":[],"forbid":[]},{"task":"Each line of an exam file has a student's name and points (`name;points`). Write a function `write_results(source, target)` that reads the file `source` and writes the file `target`, where each line has the name, the points and `pass` (50 points or more) or `fail`.\n\nIf `exam.csv` has\n\n```text\nAna;72\nBo;49\nCy;50\n```\n\nthen `write_results(\"exam.csv\", \"results.csv\")` writes this `results.csv`:\n\n```text\nAna;72;pass\nBo;49;fail\nCy;50;pass\n```\n","kind":"code","seed":"def write_results(source, target):\n    pass\n\n\nif __name__ == \"__main__\":\n    write_results(\"exam.csv\", \"results.csv\")\n","solution":"def write_results(source, target):\n    lines = []\n    with open(source) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            points = int(parts[1])\n            if points >= 50:\n                result = \"pass\"\n            else:\n                result = \"fail\"\n            lines.append(f\"{parts[0]};{points};{result}\")\n\n    with open(target, \"w\") as file:\n        for line in lines:\n            file.write(line + \"\\n\")\n\n\nif __name__ == \"__main__\":\n    write_results(\"exam.csv\", \"results.csv\")\n","hints":["Read the source file line by line: strip, split at `;`, and turn the points into an `int` to compare them with 50.","Build each new line as a string and collect them in a list, then write the list to the target file with the mode `\"w\"`.","You could also open both files at once, one `with` inside the other, and write each line as soon as you've read it."],"tests":[{"name":"write_results(\"exam.csv\", \"results.csv\")","stdin":"","call":"write_results(\"exam.csv\", \"results.csv\")\n","files":{"exam.csv":"Ana;72\nBo;49\nCy;50\n"},"writes":{"results.csv":"Ana;72;pass\nBo;49;fail\nCy;50;pass\n"},"expect":"","hidden":false},{"name":"write_results(\"retake.csv\", \"final.csv\")","stdin":"","call":"write_results(\"retake.csv\", \"final.csv\")\n","files":{"retake.csv":"Dee;12\nEli;95\n"},"writes":{"final.csv":"Dee;12;fail\nEli;95;pass\n"},"expect":"","hidden":true}],"require":[],"forbid":[]}]}