Introduction
XML namespaces prevent element name collisions when combining documents from different sources. Python's ElementTree requires you to include the full namespace URI in every tag lookup, which makes code verbose. The practical approach is to define a namespace dictionary and pass it to find() and findall(). Without this, your XPath queries silently return no results because ElementTree treats unqualified names and namespace-qualified names as different tags.
The Problem
1<!-- data.xml -->
2<root xmlns:ns="http://example.com/schema">
3 <ns:item id="1">
4 <ns:name>Widget</ns:name>
5 <ns:price>9.99</ns:price>
6 </ns:item>
7</root>
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# This finds NOTHING — namespace is missing
7items = root.findall('item')
8print(items) # []
9
10# This also finds nothing
11names = root.findall('ns:item/ns:name')
12print(names) # []
ElementTree does not understand the ns: prefix from the XML file. You must use the full namespace URI.
Method 1: Full Namespace URI (Verbose)
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# Use the full URI in curly braces
7items = root.findall('{http://example.com/schema}item')
8for item in items:
9 name = item.find('{http://example.com/schema}name').text
10 price = item.find('{http://example.com/schema}price').text
11 print(f"{name}: ${price}")
12# Widget: $9.99
This works but is hard to read with long URIs.
Method 2: Namespace Dictionary (Recommended)
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# Define namespace mapping
7ns = {'ns': 'http://example.com/schema'}
8
9# Use the prefix from your dict, not from the XML file
10items = root.findall('ns:item', ns)
11for item in items:
12 name = item.find('ns:name', ns).text
13 price = item.find('ns:price', ns).text
14 print(f"{name}: ${price}")
The prefix in your dictionary (ns) does not need to match the prefix in the XML file. It is just a local alias for the URI.
Method 3: Default Namespace
Many XML documents use a default namespace (no prefix):
1<!-- data.xml -->
2<root xmlns="http://example.com/schema">
3 <item id="1">
4 <name>Widget</name>
5 <price>9.99</price>
6 </item>
7</root>
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# Still need the namespace — even though XML has no prefix
7ns = {'d': 'http://example.com/schema'}
8
9items = root.findall('d:item', ns)
10for item in items:
11 name = item.find('d:name', ns).text
12 print(name) # Widget
You must assign a prefix (d) in your dictionary for the default namespace.
1import xml.etree.ElementTree as ET
2
3# Parse and extract namespace from root element
4tree = ET.parse('data.xml')
5root = tree.getroot()
6
7# Root tag includes the namespace: '{http://example.com/schema}root'
8print(root.tag)
9
10# Extract namespace from tag
11namespace = root.tag.split('}')[0] + '}' if '{' in root.tag else ''
12print(namespace) # {http://example.com/schema}
13
14# Use it dynamically
15items = root.findall(f'{namespace}item')
Using iterparse to Collect All Namespaces
1import xml.etree.ElementTree as ET
2
3namespaces = {}
4for event, elem in ET.iterparse('data.xml', events=['start-ns']):
5 prefix, uri = elem
6 namespaces[prefix or 'default'] = uri
7
8print(namespaces)
9# {'ns': 'http://example.com/schema'} or {'default': 'http://example.com/schema'}
Multiple Namespaces
1<root xmlns:a="http://example.com/alpha"
2 xmlns:b="http://example.com/beta">
3 <a:item>
4 <a:name>Widget</a:name>
5 <b:details>
6 <b:weight>1.5</b:weight>
7 </b:details>
8 </a:item>
9</root>
1ns = {
2 'a': 'http://example.com/alpha',
3 'b': 'http://example.com/beta'
4}
5
6items = root.findall('a:item', ns)
7for item in items:
8 name = item.find('a:name', ns).text
9 weight = item.find('b:details/b:weight', ns).text
10 print(f"{name}: {weight}kg")
Using lxml (More Powerful Alternative)
lxml has better namespace support and full XPath:
1from lxml import etree
2
3tree = etree.parse('data.xml')
4root = tree.getroot()
5
6# lxml automatically handles namespace prefixes from the document
7ns = root.nsmap
8# {'ns': 'http://example.com/schema'} or {None: 'http://example.com/schema'}
9
10# Full XPath support
11items = root.xpath('//ns:item', namespaces={'ns': 'http://example.com/schema'})
12for item in items:
13 name = item.xpath('ns:name/text()', namespaces={'ns': 'http://example.com/schema'})[0]
14 print(name)
Real-World Example: Parsing an Atom Feed
1import xml.etree.ElementTree as ET
2import urllib.request
3
4url = 'https://example.com/feed.atom'
5response = urllib.request.urlopen(url)
6tree = ET.parse(response)
7root = tree.getroot()
8
9ns = {'atom': 'http://www.w3.org/2005/Atom'}
10
11for entry in root.findall('atom:entry', ns):
12 title = entry.find('atom:title', ns).text
13 link = entry.find('atom:link', ns).get('href')
14 published = entry.find('atom:published', ns).text
15 print(f"{title} — {link} ({published})")
Common Pitfalls
Omitting the namespace in find()/findall(): Without the namespace, findall('item') returns an empty list even though the XML clearly has <item> elements. Elements in a namespace and elements without one are different tags.
Using the XML prefix instead of your dict prefix: findall('ns:item', ns) only works if your namespace dict has the key 'ns'. The prefix in the dict is what matters, not the prefix in the XML file.
Default namespace confusion: <root xmlns="..."> applies the namespace to all child elements without a prefix. You still need to specify the namespace in every find() call.
None from find() when element is missing: find() returns None if the element is not found (often due to missing namespace). Accessing .text on None raises AttributeError. Always check: elem = node.find('ns:name', ns); text = elem.text if elem is not None else ''.
ET.register_namespace() is for writing, not reading: ET.register_namespace('ns', 'http://...') controls how namespaces appear when serializing XML. It does not affect parsing or find()/findall().
Summary
Pass a namespace dict to find() and findall(): root.findall('ns:item', {'ns': 'http://...'})
The dict prefix is your local alias — it does not need to match the XML file's prefix
Default namespaces (xmlns="...") still require explicit namespace handling
Use iterparse with 'start-ns' events to extract namespaces automatically
Use lxml instead of ElementTree for full XPath and better namespace handling
Always check for None returns from find() before accessing .text