Skip to content

Commit 38af9cf

Browse files
committed
gh-144270: make Element and SubElement parameters positional-only
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.
1 parent 645f5c4 commit 38af9cf

File tree

4 files changed

+48
-4
lines changed

4 files changed

+48
-4
lines changed

Doc/library/xml.etree.elementtree.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ Functions
693693
.. versionadded:: 3.2
694694

695695

696-
.. function:: SubElement(parent, tag, attrib={}, **extra)
696+
.. function:: SubElement(parent, tag, /, attrib={}, **extra)
697697

698698
Subelement factory. This function creates an element instance, and appends
699699
it to an existing element.
@@ -704,6 +704,9 @@ Functions
704704
attributes. *extra* contains additional attributes, given as keyword
705705
arguments. Returns an element instance.
706706

707+
.. versionchanged:: next
708+
*parent* and *tag* are now positional-only parameters.
709+
707710

708711
.. function:: tostring(element, encoding="us-ascii", method="xml", *, \
709712
xml_declaration=None, default_namespace=None, \
@@ -879,7 +882,7 @@ Element Objects
879882
:noindex:
880883
:no-index:
881884

882-
.. class:: Element(tag, attrib={}, **extra)
885+
.. class:: Element(tag, /, attrib={}, **extra)
883886

884887
Element class. This class defines the Element interface, and provides a
885888
reference implementation of this interface.
@@ -889,6 +892,9 @@ Element Objects
889892
an optional dictionary, containing element attributes. *extra* contains
890893
additional attributes, given as keyword arguments.
891894

895+
.. versionchanged:: next
896+
*tag* is now a positional-only parameter.
897+
892898

893899
.. attribute:: tag
894900

Lib/test/test_xml_etree.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,19 @@ def test_simpleops(self):
381381
self.serialize_check(element,
382382
'<tag key="value"><subtag /><subtag /></tag>')
383383

384+
def test_positional_only_parameter(self):
385+
# Test Element positional-only parameters (gh-144846).
386+
387+
# 'tag' is positional-only
388+
with self.assertRaises(TypeError):
389+
ET.Element(tag='fail')
390+
391+
# 'tag' and 'attrib' as kwarg/attribute names
392+
e = ET.Element('e', attrib={'attrib': 'foo'}, tag='bar')
393+
self.assertEqual(e.tag, 'e')
394+
self.assertEqual(e.get('attrib'), 'foo')
395+
self.assertEqual(e.get('tag'), 'bar')
396+
384397
def test_cdata(self):
385398
# Test CDATA handling (etc).
386399

@@ -484,6 +497,28 @@ def test_attrib(self):
484497
self.assertEqual(ET.tostring(elem),
485498
b'<test a="&#13;" b="&#13;&#10;" c="&#09;&#10;&#13; " d="&#10;&#10;&#13;&#13;&#09;&#09; " />')
486499

500+
def test_subelement_positional_only_parameter(self):
501+
# Test SubElement positional-only parameters (gh-144270).
502+
parent = ET.Element('parent')
503+
504+
# 'parent' and 'tag' are positional-only
505+
with self.assertRaises(TypeError):
506+
ET.SubElement(parent=parent, tag='fail')
507+
with self.assertRaises(TypeError):
508+
ET.SubElement(parent, tag='fail')
509+
510+
# 'attrib' can be passed as keyword
511+
sub1 = ET.SubElement(parent, 'sub1', attrib={'key': 'value'})
512+
self.assertEqual(sub1.get('key'), 'value')
513+
514+
# 'tag' and 'parent' as kwargs become XML attributes, not func params
515+
sub2 = ET.SubElement(parent, 'sub2', attrib={'attrib': 'foo'},
516+
tag='bar', parent='baz')
517+
self.assertEqual(sub2.tag, 'sub2')
518+
self.assertEqual(sub2.get('attrib'), 'foo')
519+
self.assertEqual(sub2.get('tag'), 'bar')
520+
self.assertEqual(sub2.get('parent'), 'baz')
521+
487522
def test_makeelement(self):
488523
# Test makeelement handling.
489524

Lib/xml/etree/ElementTree.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class Element:
164164
165165
"""
166166

167-
def __init__(self, tag, attrib={}, **extra):
167+
def __init__(self, tag, /, attrib={}, **extra):
168168
if not isinstance(attrib, dict):
169169
raise TypeError("attrib must be dict, not %s" % (
170170
attrib.__class__.__name__,))
@@ -416,7 +416,7 @@ def itertext(self):
416416
yield t
417417

418418

419-
def SubElement(parent, tag, attrib={}, **extra):
419+
def SubElement(parent, tag, /, attrib={}, **extra):
420420
"""Subelement factory which creates an element instance, and appends it
421421
to an existing parent.
422422
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Made the *tag* parameter of :class:`xml.etree.ElementTree.Element` and the
2+
*parent* and *tag* parameters of :func:`xml.etree.ElementTree.SubElement`
3+
positional-only, matching the behavior of the C accelerator.

0 commit comments

Comments
 (0)