
Python Course
CSV & XML in Python
Not all data lives in databases. A huge proportion of real-world data exchange happens through files — spreadsheet exports, system reports, configuration files, API responses, government datasets. CSV and XML are two of the most widely used structured file formats, and Python has excellent built-in support for both.
This lesson covers reading and writing CSV files with the csv module and pandas, and parsing and building XML documents with xml.etree.ElementTree.
CSV — Comma-Separated Values
CSV is the simplest structured data format — each row is a line, each field separated by a delimiter (usually a comma). Despite its simplicity, CSV has edge cases: fields containing commas, quoted strings, different line endings, and varying encodings. Python's csv module handles all of these correctly.
1. Reading CSV Files
import csv, io
# In practice replace io.StringIO with: open("file.csv", newline="", encoding="utf-8")
raw = """name,department,salary,start_date
Alice,Engineering,95000,2021-03-15
Bob,Marketing,72000,2020-07-01
Charlie,Engineering,88000,2019-11-20
Diana,HR,65000,2022-01-10
"""
# csv.reader — rows as lists
reader = csv.reader(io.StringIO(raw))
header = next(reader) # consume header row
print("Columns:", header)
for row in reader:
print(row)
print()
# csv.DictReader — rows as dicts (keys = header names) — almost always preferred
reader = csv.DictReader(io.StringIO(raw))
for row in reader:
print(f"{row['name']:10} | {row['department']:12} | ${int(row['salary']):,}")
print()
# Different delimiter — tab-separated
tsv = "name age
Alice 30
Bob 25"
for row in csv.DictReader(io.StringIO(tsv), delimiter=" "):
print(row)csv.DictReaderis almost always preferred — column names as keys make code far more readable.- All values from CSV are strings — convert explicitly:
int(row["salary"]),float(row["price"]). - For real files:
open("file.csv", newline="", encoding="utf-8")— always specifynewline=""(prevents double line endings on Windows) and encoding. - Change delimiter for TSV or pipe-separated:
csv.DictReader(f, delimiter=" ").
2. Writing CSV Files
import csv, io
# csv.writer — write rows as lists
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(["product", "quantity", "price"]) # header
writer.writerows([
["Notebook", 50, 4.99],
["Pen, fine tip", 200, 1.50], # comma inside value — auto-quoted
["Desk", 10, 89.99],
])
print("csv.writer output:")
print(output.getvalue())
# csv.DictWriter — write rows as dicts
output2 = io.StringIO()
fields = ["name", "score", "grade"]
writer2 = csv.DictWriter(output2, fieldnames=fields)
writer2.writeheader() # writes field names as first row
writer2.writerows([
{"name": "Alice", "score": 92, "grade": "A"},
{"name": "Bob", "score": 78, "grade": "B"},
{"name": "Charlie", "score": 85, "grade": "B+"},
])
print("csv.DictWriter output:")
print(output2.getvalue())
# Writing to a real file:
# with open("output.csv", "w", newline="", encoding="utf-8") as f:
# writer = csv.DictWriter(f, fieldnames=fields)
# writer.writeheader()
# writer.writerows(data)csv.writerautomatically quotes fields containing the delimiter — you never handle this yourself.DictWriter.writeheader()writes field names as the first row automatically.writer.writerows(list)writes multiple rows in one call.- Always use
newline=""when opening a file for writing CSV.
3. CSV with pandas
For analysis or large files, pandas is far more powerful — it loads the file into a DataFrame with automatic type inference, filtering, aggregation, and export built in.
import pandas as pd, io
raw = """name,department,salary,start_date
Alice,Engineering,95000,2021-03-15
Bob,Marketing,72000,2020-07-01
Charlie,Engineering,88000,2019-11-20
Diana,HR,65000,2022-01-10
Eve,Engineering,102000,2018-05-30
"""
df = pd.read_csv(io.StringIO(raw), parse_dates=["start_date"])
print(df.dtypes, "
")
# Filter and display
eng = df[df["department"] == "Engineering"]
print(f"Engineering ({len(eng)} people):")
print(eng[["name", "salary"]].to_string(index=False))
# Aggregation
print(f"
Overall avg: ${df['salary'].mean():,.0f}")
print(df.groupby("department")["salary"].mean().sort_values(ascending=False))
# Export — index=False omits the row-number column
df.to_csv("employees_export.csv", index=False)
print("
Exported to employees_export.csv")pd.read_csv()infers types — integers stayint64, dates parse withparse_dates=.df.groupby("col")["col2"].mean()— group-level aggregation in one line.df.to_csv("file.csv", index=False)— export without the row-number index column.- Use
pandaswhenever you need filtering, grouping, merging, or any analysis beyond simple I/O.
XML — eXtensible Markup Language
XML uses nested tags to represent hierarchical data. It is used in configuration files, document formats (Word, SVG), SOAP web services, RSS feeds, and enterprise data exchange. Python's xml.etree.ElementTree is built in and covers most use cases.
4. Parsing XML
import xml.etree.ElementTree as ET
xml_data = """
<library>
<book id="1" genre="fiction">
<title>The Great Gatsby</title>
<author>F. Scott Fitzgerald</author>
<year>1925</year>
<price>12.99</price>
</book>
<book id="2" genre="non-fiction">
<title>Sapiens</title>
<author>Yuval Noah Harari</author>
<year>2011</year>
<price>16.99</price>
</book>
<book id="3" genre="fiction">
<title>1984</title>
<author>George Orwell</author>
<year>1949</year>
<price>9.99</price>
</book>
</library>"""
root = ET.fromstring(xml_data) # parse from string
# ET.parse("file.xml").getroot() — parse from file
print(f"Root: <{root.tag}> | {len(root)} children
")
for book in root.findall("book"):
book_id = book.get("id")
genre = book.get("genre", "unknown") # default if attr missing
title = book.find("title").text
author = book.find("author").text
price = float(book.find("price").text)
print(f"[{book_id}] {title} — {author} | ${price:.2f} ({genre})")
# XPath attribute filter
print("
Fiction only:")
for book in root.findall("book[@genre='fiction']"):
print(" ", book.find("title").text)
# .//
tag — search all descendants at any depth
all_prices = [float(p.text) for p in root.findall(".//price")]
print(f"
Total library value: ${sum(all_prices):.2f}")root.findall("tag")— direct children;root.findall(".//tag")— all descendants at any depth.element.find("tag")— first match orNone; always check forNonebefore calling.text.element.get("attr")— attribute value orNone;element.get("attr", default)— with fallback.element.text— the text content between opening and closing tags.- XPath
[@attr='value']filters elements by attribute value.
5. Building and Modifying XML
import xml.etree.ElementTree as ET
root = ET.Element("inventory")
root.set("version", "1.0") # attribute on root element
items = [
{"sku": "NB001", "name": "Notebook", "qty": 150, "price": 4.99},
{"sku": "PN002", "name": "Pen", "qty": 500, "price": 1.50},
{"sku": "DS003", "name": "Desk", "qty": 20, "price": 89.99},
]
for item in items:
el = ET.SubElement(root, "item", sku=item["sku"])
ET.SubElement(el, "name").text = item["name"]
ET.SubElement(el, "qty").text = str(item["qty"])
ET.SubElement(el, "price").text = f"{item['price']:.2f}"
ET.indent(root, space=" ") # pretty-print (Python 3.9+)
print(ET.tostring(root, encoding="unicode"))
# Write to file
ET.ElementTree(root).write("inventory.xml", encoding="unicode", xml_declaration=True)
# Modify in-place — update Notebook price
for item in root.findall("item"):
if item.find("name").text == "Notebook":
item.find("price").text = "5.49"
print("Updated:", ET.tostring(item, encoding="unicode"))ET.Element("tag")— root element;root.set("attr", "val")— sets an attribute.ET.SubElement(parent, "tag", attr=val)— creates a child element with optional attributes.ET.indent(root)— adds whitespace for human-readable output (Python 3.9+).- Modify in-place by finding the element and reassigning its
.textor attributes. - For complex XML with namespaces or XSLT, use the third-party
lxmllibrary.
Quick Reference Table
| Tool | Format | Best Used For |
|---|---|---|
csv.DictReader | CSV | Reading CSV rows as named dicts |
csv.DictWriter | CSV | Writing dicts as CSV rows |
pandas.read_csv | CSV | Analysis, filtering, aggregation |
ET.fromstring() | XML | Parse XML from a string |
ET.parse() | XML | Parse XML from a file |
ET.SubElement() | XML | Build XML trees programmatically |
.//tag XPath | XML | Find all descendants at any depth |
Practice
What is the difference between csv.reader and csv.DictReader?
Why must you specify newline="" when opening a CSV file?
In ElementTree, what property retrieves the text between an element's opening and closing tags?
What does df.to_csv("file.csv", index=False) do differently from the default?
Which ElementTree function adds a child element to an existing parent?
What XPath expression in findall() searches all descendants at any depth?
Quick Quiz
What data type does csv.DictReader return for each row?
What does pd.read_csv(..., parse_dates=["start_date"]) do?
In ElementTree, what is the difference between find() and findall()?
How does csv.writer handle a field value that contains a comma?
Which Python version introduced ET.indent() for pretty-printing XML?
How do you safely read an XML attribute that might not exist, with a fallback value?