]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-144270: Make SubElement parent and tag positional-only (GH-144845)
authorNeko Asakura <neko.asakura@outlook.com>
Mon, 30 Mar 2026 16:42:24 +0000 (00:42 +0800)
committerGitHub <noreply@github.com>
Mon, 30 Mar 2026 16:42:24 +0000 (19:42 +0300)
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.

Doc/library/xml.etree.elementtree.rst
Lib/test/test_xml_etree.py
Lib/xml/etree/ElementTree.py
Misc/NEWS.d/next/Library/2026-02-19-16-34-18.gh-issue-144270.wJRtSr.rst [new file with mode: 0644]

index 919d4c595bf7937fce694fa8dd3061f6414532fa..bbb15ce5e758c6aafb258ad4ed7893def107cd7c 100644 (file)
@@ -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
 
index 5b06e422672b1dde3891db6fec465fcd00e8d9a9..b380d0276b0169e21af30999c3da1c3abb477880 100644 (file)
@@ -381,6 +381,19 @@ class ElementTreeTest(unittest.TestCase):
         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 @@ class ElementTreeTest(unittest.TestCase):
         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.
 
index 57c5b64ea3ba70a99009b194275bd94f1fe96455..85766e02b531ce2a8d6354ab671357d090a38347 100644 (file)
@@ -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 @@ class Element:
                 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 (file)
index 0000000..b8a4374
--- /dev/null
@@ -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.