XML has fallen out of fashion relative to JSON for new APIs, but it remains the format for SOAP APIs, many enterprise integrations, RSS/Atom feeds, SVG, and countless legacy systems still in production. This lesson covers xml.etree.ElementTree, the standard library's XML toolkit.
1Why XML Still Matters in Professional Python Work
JSON has largely won the popularity contest for new, greenfield APIs ā it's more compact, has a simpler specification, and maps more directly onto common programming language data structures. But an enormous amount of software already in production, and a specific set of domains, remain XML-based: SOAP APIs (still common in enterprise, banking, and government integrations), many legacy enterprise systems that predate JSON's dominance, configuration formats for major tools (Maven's pom.xml, Android's layout files), and entire document formats built on XML ā SVG, RSS/Atom feeds, and the underlying format inside .docx/.xlsx files (which are actually ZIP archives containing XML, tying directly into the next lesson).
A professional Python developer working on integration code, enterprise software, or anything touching a sufficiently established system will encounter XML with real regularity, regardless of how much new API design has shifted toward JSON ā treating XML as obsolete or unnecessary to learn leaves a genuine, common integration surface unaddressed.
xml.etree.ElementTree (commonly imported as ET), in the standard library since 3.3 (no external dependency required, unlike YAML), is the standard, lightweight tool for both parsing existing XML and constructing new XML documents ā this lesson's baseline toolkit for professional XML work.
import xml.etree.ElementTree as ET
tree = ET.parse("catalog.xml")
root = tree.getroot()
for book in root.findall("book"):
title = book.find("title").text
price = book.find("price").text
print(f"{title}: ${price}")All XML, all still in active production use
2Navigating a Parsed Tree: Elements, Children, Attributes, and Text
ET.parse("catalog.xml").getroot() returns the tree's root Element, and .find(tag)/.findall(tag) ā using a simplified, XPath-inspired query syntax ā locate a single matching child element or all matching child elements, respectively. root.findall("book") returns every direct <book> child of the root; more elaborate paths like ".//title" (searching recursively at any depth) are also supported for more complex document structures.
A single XML element can carry two genuinely different kinds of data simultaneously, and it's essential to know which access pattern applies to which: attributes ā the key="value" pairs written inside an element's opening tag (id="bk101", category="fiction") ā are read with .get(attribute_name), returning the attribute's string value or None if it isn't present. Child element text content ā the text between an element's opening and closing tags, like The Great Novel inside <title>...</title> ā is read via .find(child_tag).text on the parent, first locating the child element and then reading its .text property.
Conflating these two access patterns is a common source of confusion for developers newer to XML specifically because JSON has no equivalent distinction ā every value in JSON is 'just a value' at some key, while XML elements genuinely have two separate, co-existing places data can live, and reading the wrong one (trying .get("title") on book instead of .find("title").text) simply returns None rather than raising an obviously diagnosable error.
# <book id="bk101" category="fiction">
# <title>The Great Novel</title>
# </book>
book_id = book.get("id") # 'bk101' -- an attribute
category = book.get("category") # 'fiction' -- another attribute
title = book.find("title").text # 'The Great Novel' -- child element's TEXTbook.find("title").text ā child element's text
3Building XML From Scratch: Element and SubElement
Constructing a new XML document programmatically mirrors the reading API's shape: ET.Element("catalog") creates a standalone root element; ET.SubElement(root, "book", id="bk101") creates a new child element attached directly to root, with any keyword arguments (id="bk101") becoming that new element's attributes, and returns the newly-created element so you can immediately continue building on it ā setting its .text, or creating further SubElements nested inside it.
ET.tostring(root, encoding="unicode") serializes the fully-constructed tree back into actual XML text ā the encoding="unicode" argument specifically requests a Python str result rather than the function's default of returning bytes, which matters when you intend to further process, log, or embed the XML string directly in Python code rather than write it straight to a binary file handle.
This programmatic construction pattern ā build a tree of Element/SubElement objects, then serialize once at the end ā is the standard approach for generating XML output from Python data (converting a list of Python objects into an XML export, for instance), and it mirrors the same 'build a data structure, serialize once' philosophy used throughout this section for JSON and YAML: manipulate structured, native Python objects throughout your logic, and only convert to the final text format as the very last step.
import xml.etree.ElementTree as ET
root = ET.Element("catalog")
book = ET.SubElement(root, "book", id="bk101")
ET.SubElement(book, "title").text = "The Great Novel"
ET.SubElement(book, "price").text = "29.99"
xml_string = ET.tostring(root, encoding="unicode")
print(xml_string) # '<catalog><book id="bk101"><title>The Great Novel</title>...'Build the tree, serialize once with tostring()
4Step-by-Step Breakdown
JSON won the new-API popularity contest, but XML runs an enormous amount of the enterprise and legacy software you'll eventually need to integrate with. Let's parse and build it correctly.
ElementTree.parse() reads an XML file into a navigable tree -- .find() and .findall() use XPath-like expressions to locate elements.
Attributes (not just text content) are accessed via .get() -- XML elements can carry BOTH child elements AND attributes simultaneously.
Checkpoint: For <book id="bk101"><title>The Great Novel</title></book>, how do you access "bk101" versus "The Great Novel"?
- ābook.get("id") for the attribute "bk101"; book.find("title").text for the child element's text content
- āBoth are accessed the same way, via .text
Building XML from scratch uses Element/SubElement -- and ET.tostring() serializes the tree back to actual XML text.
Checkpoint: What does ET.SubElement(book, "title") do?
- āCreates a new child element named "title" under the book element, and returns it
- āSearches for an existing "title" child element under book
XML rounds out the structured-text formats; ZIP Files shifts to binary archive handling.
Navigate Real XML. Finish get_child_text(): find(tag).text pulls out a child element's text content.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Best Practices
Know the difference between an element's attributes (.get()) and a child element's text (.find().text)
These are two genuinely different places data lives in XML ā conflating them silently returns None rather than raising an obvious error, making the distinction worth internalizing deliberately.
Build XML programmatically with Element/SubElement rather than string concatenation
String-templating XML by hand risks malformed output (unescaped special characters, mismatched tags) that ElementTree's API guarantees is well-formed by construction.
Frequent Bugs
Calling .get('title') on an element expecting to retrieve a child element's TEXT content, when 'title' is actually a child element (not an attribute), silently getting None instead of the expected value.
Use .find("title").text for a child element's text content, and .get("attribute_name") only for genuine attributes written inside the opening tag.
Real-World Examples
Parsing an RSS Feed
A content aggregator needs to parse an RSS feed (an XML format) to extract each article's title, link, and publication date.
import xml.etree.ElementTree as ET
tree = ET.parse("feed.xml")
root = tree.getroot()
for item in root.findall(".//item"):
title = item.find("title").text
link = item.find("link").text
pub_date = item.find("pubDate").text
print(f"{title} ({pub_date}): {link}")