pyguides

xml.etree.ElementTree module

Overview

xml.etree.ElementTree provides a lightweight API for parsing and generating XML. You work with a tree of elements, each representing a single node — with a tag, attributes, and child elements. The module ships with Python 2.7+ and requires no installation.

It is not the fastest XML library available, and it is not safe for untrusted XML (use defusedxml for that). But for reading configuration files, SOAP responses, or structured data from REST APIs, it is usually the right tool.

Parsing XML

From a string

import xml.etree.ElementTree as ET

xml_data = """<config>
    <database host="localhost" port="5432"/>
    <cache enabled="true" ttl="300"/>
</config>"""

root = ET.fromstring(xml_data)
print(root.tag)        # => 'config'
print(root[0].tag)     # => 'database'
print(root[0].attrib)  # => {'host': 'localhost', 'port': '5432'}

ET.fromstring() parses a string directly into an element tree. It raises ParseError if the XML is malformed.

From a file or URL

tree = ET.parse("config.xml")
root = tree.getroot()

Use ET.parse() for file paths. For HTTP responses or other file-like objects, parse from a stream:

import urllib.request

response = urllib.request.urlopen("https://example.com/data.xml")
root = ET.parse(response).getroot()

Iterating over all elements

root = ET.parse("config.xml").getroot()

for elem in root.iter():
    print(elem.tag, elem.attrib)

# config {}
# database {'host': 'localhost', 'port': '5432'}
# cache {'enabled': 'true', 'ttl': '300'}

iter() traverses the entire tree depth-first. iterfind() limits traversal to elements matching a tag or path.

Element Objects

Each element has:

  • tag — the element name (a string)
  • attrib — a dict of attributes
  • text — the text content between the opening and closing tags
  • tail — the text after the closing tag, up to the next sibling
  • children — sub-elements accessed by index or iteration

Accessing attributes and text

root = ET.fromstring('<product name="Widget" price="9.99" quantity="100"/>')

root.attrib['name']        # => 'Widget'
root.attrib.get('price')   # => '9.99'
root.attrib.get('weight')  # => None (missing attribute)
root.attrib.get('weight', 'N/A')  # => 'N/A' (with default)

root.text                  # => None (no text content here)
config = ET.fromstring("""<config>
    <database host="localhost" port="5432"/>
</config>""")

db = config[0]
print(db.tag)           # => 'database'
print(db.attrib['host']) # => 'localhost'

# Children iteration
for child in config:
    print(child.tag, child.attrib)

Building XML

Creating elements

root = ET.Element("person")
root.attrib["id"] = "p001"

name = ET.SubElement(root, "name")
name.text = "Alice"

age = ET.SubElement(root, "age")
age.text = "30"

# Pretty print with indentation
ET.indent(root)
ET.tostring(root, encoding="unicode")
# <person id="p001">
#   <name>Alice</name>
#   <age>30</age>
# </person>

SubElement() creates a child element and appends it in one step. Set .text on the element for its text content.

Writing to a file

tree = ET.ElementTree(root)
tree.write("output.xml", encoding="utf-8", xml_declaration=True)

The file is written with UTF-8 encoding by default.

XPath Support

ElementTree supports a subset of XPath for finding elements:

root = ET.parse("inventory.xml").getroot()

# Find all <item> elements anywhere in the tree
items = root.findall(".//item")

# Find direct children with a specific tag
products = root.findall("product")

# Find first matching element
first = root.find(".//item[@id='001']")
ExpressionMeaning
tagDirect child with that tag
.Current element
..Parent element
//tagAny descendant with that tag
[@attr]Element with that attribute
[@attr='value']Element with exact attribute match

Serialization

To string

root = ET.Element("config")
ET.SubElement(root, "setting").text = "value"

ET.tostring(root)                    # bytes
ET.tostring(root, encoding="unicode")  # string

To file

tree = ET.ElementTree(root)
tree.write("config.xml", encoding="utf-8")

Common Use Cases

Processing RSS feeds

import xml.etree.ElementTree as ET

rss_xml = """<rss version="2.0">
    <channel>
        <item><title>First Post</title><link>https://example.com/1</link></item>
        <item><title>Second Post</title><link>https://example.com/2</link></item>
    </channel>
</rss>"""

root = ET.fromstring(rss_xml)
for item in root.findall(".//item"):
    title = item.find("title").text
    link = item.find("link").text
    print(f"{title}: {link}")

Reading configuration files

import xml.etree.ElementTree as ET

config = ET.parse("app_config.xml").getroot()
for section in config:
    print(f"Section: {section.tag}")
    for key, val in section.attrib.items():
        print(f"  {key} = {val}")

Modifying XML and writing back

tree = ET.parse("data.xml")
root = tree.getroot()

# Update an attribute
for elem in root.iter("database"):
    elem.attrib["port"] = "5433"

# Add a new child
new_elem = ET.SubElement(root.find("settings"), "option")
new_elem.text = "enabled"

# Write back
tree.write("data_modified.xml", encoding="utf-8")

Gotchas

Attribute order is not preserved. XML attributes are stored in a dict, which has no guaranteed order in Python versions before 3.7. If you need a specific output order, use a different library.

Namespaces can confuse find. When XML uses namespaces, bare tag names may not match:

<root xmlns:ns="http://example.com">
    <ns:item>text</ns:item>
</root>

Use the namespace URI in your searches:

ns = {"ex": "http://example.com"}
root.find("ex:item", ns)  # finds the element

Register namespace prefixes to keep output clean:

ET.register_namespace("ex", "http://example.com")

Not safe for untrusted input. ElementTree is vulnerable to billion laugh attacks and external entity attacks. For untrusted XML, use the defusedxml library instead, which disables these features.

tostring() strips the XML declaration by default. Pass xml_declaration=True to include it:

ET.tostring(root, encoding="utf-8", xml_declaration=True)

See Also