https://github.com/python/cpython/commit/afd8113e9d7744e52ae82093628d373d6ecb9897
commit: afd8113e9d7744e52ae82093628d373d6ecb9897
branch: main
author: Neko Asakura <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-03-30T19:42:24+03:00
summary:

gh-144270: Make SubElement parent and tag positional-only (GH-144845)

The C accelerator implementations use PyArg_ParseTuple, which
inherently enforces positional-only parameters. The Python fallback
allowed these as keyword arguments, creating a behavioral mismatch.

Make the tag parameter of Element.__init__ and the parent and tag
parameters of SubElement positional-only to align with the C
accelerator.

files:
A Misc/NEWS.d/next/Library/2026-02-19-16-34-18.gh-issue-144270.wJRtSr.rst
M Doc/library/xml.etree.elementtree.rst
M Lib/test/test_xml_etree.py
M Lib/xml/etree/ElementTree.py

diff --git a/Doc/library/xml.etree.elementtree.rst 
b/Doc/library/xml.etree.elementtree.rst
index 919d4c595bf793..bbb15ce5e758c6 100644
--- a/Doc/library/xml.etree.elementtree.rst
+++ b/Doc/library/xml.etree.elementtree.rst
@@ -691,7 +691,7 @@ Functions
    .. versionadded:: 3.2
 
 
-.. function:: SubElement(parent, tag, attrib={}, **extra)
+.. function:: SubElement(parent, tag, /, attrib={}, **extra)
 
    Subelement factory.  This function creates an element instance, and appends
    it to an existing element.
@@ -705,6 +705,9 @@ Functions
    .. versionchanged:: 3.15
       *attrib* can now be a :class:`frozendict`.
 
+   .. versionchanged:: next
+      *parent* and *tag* are now positional-only parameters.
+
 
 .. function:: tostring(element, encoding="us-ascii", method="xml", *, \
                        xml_declaration=None, default_namespace=None, \
@@ -880,7 +883,7 @@ Element Objects
    :noindex:
    :no-index:
 
-.. class:: Element(tag, attrib={}, **extra)
+.. class:: Element(tag, /, attrib={}, **extra)
 
    Element class.  This class defines the Element interface, and provides a
    reference implementation of this interface.
@@ -893,6 +896,9 @@ Element Objects
    .. versionchanged:: 3.15
       *attrib* can now be a :class:`frozendict`.
 
+   .. versionchanged:: next
+      *tag* is now a positional-only parameter.
+
 
    .. attribute:: tag
 
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 5b06e422672b1d..b380d0276b0169 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -381,6 +381,19 @@ def test_simpleops(self):
         self.serialize_check(element,
                 '<tag key="value"><subtag /><subtag /></tag>')
 
+    def test_positional_only_parameter(self):
+        # Test Element positional-only parameters (gh-144846).
+
+        # 'tag' is positional-only
+        with self.assertRaises(TypeError):
+            ET.Element(tag='fail')
+
+        # 'tag' and 'attrib' as kwarg/attribute names
+        e = ET.Element('e', attrib={'attrib': 'foo'}, tag='bar')
+        self.assertEqual(e.tag, 'e')
+        self.assertEqual(e.get('attrib'), 'foo')
+        self.assertEqual(e.get('tag'), 'bar')
+
     def test_cdata(self):
         # Test CDATA handling (etc).
 
@@ -484,6 +497,28 @@ def test_attrib(self):
         self.assertEqual(ET.tostring(elem),
                 b'<test a="&#13;" b="&#13;&#10;" c="&#09;&#10;&#13; " 
d="&#10;&#10;&#13;&#13;&#09;&#09;  " />')
 
+    def test_subelement_positional_only_parameter(self):
+        # Test SubElement positional-only parameters (gh-144270).
+        parent = ET.Element('parent')
+
+        # 'parent' and 'tag' are positional-only
+        with self.assertRaises(TypeError):
+            ET.SubElement(parent=parent, tag='fail')
+        with self.assertRaises(TypeError):
+            ET.SubElement(parent, tag='fail')
+
+        # 'attrib' can be passed as keyword
+        sub1 = ET.SubElement(parent, 'sub1', attrib={'key': 'value'})
+        self.assertEqual(sub1.get('key'), 'value')
+
+        # 'tag' and 'parent' as kwargs become XML attributes, not func params
+        sub2 = ET.SubElement(parent, 'sub2', attrib={'attrib': 'foo'},
+                             tag='bar', parent='baz')
+        self.assertEqual(sub2.tag, 'sub2')
+        self.assertEqual(sub2.get('attrib'), 'foo')
+        self.assertEqual(sub2.get('tag'), 'bar')
+        self.assertEqual(sub2.get('parent'), 'baz')
+
     def test_makeelement(self):
         # Test makeelement handling.
 
diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py
index 57c5b64ea3ba70..85766e02b531ce 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -164,7 +164,7 @@ class Element:
 
     """
 
-    def __init__(self, tag, attrib={}, **extra):
+    def __init__(self, tag, /, attrib={}, **extra):
         if not isinstance(attrib, (dict, frozendict)):
             raise TypeError("attrib must be dict or frozendict, not %s" % (
                 attrib.__class__.__name__,))
@@ -416,7 +416,7 @@ def itertext(self):
                 yield t
 
 
-def SubElement(parent, tag, attrib={}, **extra):
+def SubElement(parent, tag, /, attrib={}, **extra):
     """Subelement factory which creates an element instance, and appends it
     to an existing parent.
 
diff --git 
a/Misc/NEWS.d/next/Library/2026-02-19-16-34-18.gh-issue-144270.wJRtSr.rst 
b/Misc/NEWS.d/next/Library/2026-02-19-16-34-18.gh-issue-144270.wJRtSr.rst
new file mode 100644
index 00000000000000..b8a4374bc2d3ca
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-19-16-34-18.gh-issue-144270.wJRtSr.rst
@@ -0,0 +1,3 @@
+Made the *tag* parameter of :class:`xml.etree.ElementTree.Element` and the
+*parent* and *tag* parameters of :func:`xml.etree.ElementTree.SubElement`
+positional-only, matching the behavior of the C accelerator.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to