From e7c93b7e5c2cb3ffe076845edd6b5c287af3bc4c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 2 Sep 2026 15:30:21 +0300 Subject: [PATCH 1/9] gh-156658: Only XML white space characters are treated as white space (GH-156659) XML defines white space as " \t\r\n" (see XML 1.0, 2.3), but str.strip() also strips other characters, such as U+00A0. Such characters could be lost in ElementTree.indent(), in canonicalize(strip_text=True), and when parsing with the whitespace-in-element-content feature turned off. --- Lib/test/test_minidom.py | 24 +++++++++++++++++++ Lib/test/test_xml_etree.py | 14 +++++++++++ Lib/xml/dom/expatbuilder.py | 6 +++-- Lib/xml/dom/minidom.py | 5 +++- Lib/xml/etree/ElementTree.py | 13 ++++++---- ...-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst | 5 ++++ 6 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index 3735a6046891ea..e204bdc7dc672d 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -642,6 +642,30 @@ def test_toprettyxml_preserves_content_of_text_node(self): dom.getElementsByTagName('B')[0].childNodes[0].toxml(), dom2.getElementsByTagName('B')[0].childNodes[0].toxml()) + def test_isWhitespaceInElementContent(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + dom = parseString(']>' + ' x\xa0') + children = dom.documentElement.childNodes + self.assertTrue(children[0].isWhitespaceInElementContent) + self.assertFalse(children[2].isWhitespaceInElementContent) + dom.unlink() + + def test_remove_whitespace_in_element_content(self): + from xml.dom.xmlbuilder import DOMBuilder, DOMInputSource + builder = DOMBuilder() + builder.setFeature("whitespace-in-element-content", False) + source = DOMInputSource() + source.byteStream = io.BytesIO( + b']>' + b' x\xc2\xa0') + dom = builder.parse(source) + children = dom.documentElement.childNodes + # ignorable whitespace is removed, other characters are not + self.assertEqual([node.nodeName for node in children], ['b', '#text']) + self.assertEqual(children[1].data, '\xa0') + dom.unlink() + def testProcessingInstruction(self): dom = parseString('') pi = dom.documentElement.firstChild diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index a0337de58b23ae..f9ff8c4c3541ed 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -845,6 +845,15 @@ def test_indent_space_caching(self): len({id(el.tail) for el in elem.iter()}), ) + def test_indent_non_xml_whitespace(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + elem = ET.XML('\xa0

text

\xa0') + ET.indent(elem) + self.assertEqual( + ET.tostring(elem), + b' \n

text

 \n' + ) + def test_indent_level(self): elem = ET.XML("

pre
post

text

") with self.assertRaises(ValueError): @@ -4900,6 +4909,11 @@ def test_simple_roundtrip(self): xml = '' self.assertEqual(c14n_roundtrip(xml), xml) + def test_c14n_strip_non_xml_whitespace(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + self.assertEqual(c14n_roundtrip(" \xa0x\xa0 ", strip_text=True), + "\xa0x\xa0") + def test_c14n_exclusion(self): xml = textwrap.dedent("""\ diff --git a/Lib/xml/dom/expatbuilder.py b/Lib/xml/dom/expatbuilder.py index d56b2ddfdb2569..e3917c1cc68288 100644 --- a/Lib/xml/dom/expatbuilder.py +++ b/Lib/xml/dom/expatbuilder.py @@ -30,7 +30,8 @@ from xml.dom import xmlbuilder, minidom, Node from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE from xml.parsers import expat -from xml.dom.minidom import _append_child, _set_attribute_node +from xml.dom.minidom import (_append_child, _set_attribute_node, + _XML_WHITESPACE) from xml.dom.NodeFilter import NodeFilter TEXT_NODE = Node.TEXT_NODE @@ -413,7 +414,8 @@ def _handle_white_text_nodes(self, node, info): # whitespace. L = [] for child in node.childNodes: - if child.nodeType == TEXT_NODE and not child.data.strip(): + if (child.nodeType == TEXT_NODE + and not child.data.strip(_XML_WHITESPACE)): L.append(child) # Remove ignorable whitespace from the tree. diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 5fd3911bd3c9eb..7639fa14c5050f 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -31,6 +31,9 @@ _nodeTypes_with_children = (xml.dom.Node.ELEMENT_NODE, xml.dom.Node.ENTITY_REFERENCE_NODE) +# The white space characters of the XML specification (see XML 1.0, 2.3). +_XML_WHITESPACE = " \t\r\n" + class Node(xml.dom.Node): namespaceURI = None # this is non-null only for elements and attributes @@ -1209,7 +1212,7 @@ def replaceWholeText(self, content): return None def _get_isWhitespaceInElementContent(self): - if self.data.strip(): + if self.data.strip(_XML_WHITESPACE): return False elem = _get_containing_element(self) if elem is None: diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 1cba3eae7ad629..2fee57df59fdd8 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -101,6 +101,9 @@ from . import ElementPath +# The white space characters of the XML specification (see XML 1.0, 2.3). +_XML_WHITESPACE = " \t\r\n" + class ParseError(SyntaxError): """An error when parsing an XML document. @@ -1209,17 +1212,17 @@ def _indent_children(elem, level): child_indentation = indentations[level] + space indentations.append(child_indentation) - if not elem.text or not elem.text.strip(): + if not elem.text or not elem.text.strip(_XML_WHITESPACE): elem.text = child_indentation for child in elem: if len(child): _indent_children(child, child_level) - if not child.tail or not child.tail.strip(): + if not child.tail or not child.tail.strip(_XML_WHITESPACE): child.tail = child_indentation # Dedent after the last child by overwriting the previous indentation. - if not child.tail.strip(): + if not child.tail.strip(_XML_WHITESPACE): child.tail = indentations[level] _indent_children(tree, 0) @@ -1724,7 +1727,7 @@ def _default(self, text): if prefix == ">": self._doctype = None return - text = text.strip() + text = text.strip(_XML_WHITESPACE) if not text: return self._doctype.append(text) @@ -1940,7 +1943,7 @@ def _flush(self, _join_text=''.join): data = _join_text(self._data) del self._data[:] if self._strip_text and not self._preserve_space[-1]: - data = data.strip() + data = data.strip(_XML_WHITESPACE) if self._pending_start is not None: args, self._pending_start = self._pending_start, None qname_text = data if data and _looks_like_prefix_name(data) else None diff --git a/Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst b/Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst new file mode 100644 index 00000000000000..3f2f105116c183 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst @@ -0,0 +1,5 @@ +:mod:`xml.dom` and :mod:`xml.etree.ElementTree` no longer treat characters +which are not white space in XML (such as U+00A0) as white space. Previously +they could be lost in :func:`~xml.etree.ElementTree.indent`, +:func:`~xml.etree.ElementTree.canonicalize` with ``strip_text=True``, and when +parsing with the ``whitespace-in-element-content`` feature turned off. From 39a9a47516abf8a68cb3239f968c2b2135c3a5a7 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 2 Sep 2026 18:26:17 +0530 Subject: [PATCH 2/9] gh-156228: Use PyMem_Raw allocators for OpenSSL in `_ssl` and `_hashlib` (#156229) --- Makefile.pre.in | 4 +- ...-08-22-10-30-00.gh-issue-156228.k3Rmxw.rst | 4 ++ Modules/_hashopenssl.c | 2 + Modules/_openssl_mem.h | 56 +++++++++++++++++++ Modules/_ssl.c | 2 + PCbuild/_hashlib.vcxproj | 3 + PCbuild/_hashlib.vcxproj.filters | 8 +++ PCbuild/_ssl.vcxproj | 3 + PCbuild/_ssl.vcxproj.filters | 8 +++ 9 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-22-10-30-00.gh-issue-156228.k3Rmxw.rst create mode 100644 Modules/_openssl_mem.h diff --git a/Makefile.pre.in b/Makefile.pre.in index b2bd89039e1230..78a486623181fa 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -3441,7 +3441,7 @@ MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h MODULE__CTYPES_TEST_DEPS=$(srcdir)/Modules/_ctypes/_ctypes_test_generated.c.h MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@ MODULE__ELEMENTTREE_DEPS=$(srcdir)/Modules/pyexpat.c @LIBEXPAT_INTERNAL@ -MODULE__HASHLIB_DEPS=$(srcdir)/Modules/hashlib.h +MODULE__HASHLIB_DEPS=$(srcdir)/Modules/hashlib.h $(srcdir)/Modules/_openssl_mem.h MODULE__IO_DEPS=$(srcdir)/Modules/_io/_iomodule.h MODULE__REMOTE_DEBUGGING_DEPS=$(srcdir)/Modules/_remote_debugging/_remote_debugging.h $(srcdir)/Modules/_remote_debugging/gc_stats.h @@ -3460,7 +3460,7 @@ MODULE__HMAC_DEPS=$(srcdir)/Modules/hashlib.h $(LIBHACL_HMAC_HEADERS) $(LIBHACL_ MODULE__HMAC_LDEPS=$(LIBHACL_HMAC_LIB_@LIBHACL_LDEPS_LIBTYPE@) MODULE__SOCKET_DEPS=$(srcdir)/Modules/socketmodule.h $(srcdir)/Modules/addrinfo.h $(srcdir)/Modules/getaddrinfo.c $(srcdir)/Modules/getnameinfo.c -MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h +MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_openssl_mem.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h MODULE__TESTCAPI_DEPS=$(srcdir)/Modules/_testcapi/parts.h $(srcdir)/Modules/_testcapi/util.h MODULE__TESTLIMITEDCAPI_DEPS=$(srcdir)/Modules/_testlimitedcapi/testcapi_long.h $(srcdir)/Modules/_testlimitedcapi/parts.h $(srcdir)/Modules/_testlimitedcapi/util.h MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Parser/tokenizer/cursor.h $(srcdir)/Parser/tokenizer/source.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h diff --git a/Misc/NEWS.d/next/Library/2026-08-22-10-30-00.gh-issue-156228.k3Rmxw.rst b/Misc/NEWS.d/next/Library/2026-08-22-10-30-00.gh-issue-156228.k3Rmxw.rst new file mode 100644 index 00000000000000..536bcd44c92111 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-22-10-30-00.gh-issue-156228.k3Rmxw.rst @@ -0,0 +1,4 @@ +The :mod:`ssl` and :mod:`hashlib` modules now route OpenSSL memory +allocations through the Python raw memory allocators, making OpenSSL memory +usage visible to :mod:`tracemalloc` and to custom allocators installed with +:c:func:`PyMem_SetAllocator`. diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index f895c9037485c4..d23767afeb96cf 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -27,6 +27,7 @@ #include "pycore_strhex.h" // _Py_strhex() #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_RELAXED #include "hashlib.h" +#include "_openssl_mem.h" /* EVP is the preferred interface to hashing in OpenSSL */ #include @@ -2933,5 +2934,6 @@ static struct PyModuleDef _hashlibmodule = { PyMODINIT_FUNC PyInit__hashlib(void) { + _PyOpenSSL_SetupMemFunctions(); return PyModuleDef_Init(&_hashlibmodule); } diff --git a/Modules/_openssl_mem.h b/Modules/_openssl_mem.h new file mode 100644 index 00000000000000..5284d9b5e63361 --- /dev/null +++ b/Modules/_openssl_mem.h @@ -0,0 +1,56 @@ +// Route OpenSSL allocations through the raw memory allocators. +// Shared by the _ssl and _hashlib modules. + +#ifndef Py_OPENSSL_MEM_H +#define Py_OPENSSL_MEM_H + +#include "Python.h" + +#include // CRYPTO_set_mem_functions() + +// LibreSSL stubs out CRYPTO_set_mem_functions() and BoringSSL lacks it. +// AWS-LC has it, but unlike OpenSSL it does not refuse to install hooks +// after the first allocation, so earlier size-prefixed allocations would +// be freed with the wrong allocator. +#if !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) \ + && !defined(OPENSSL_IS_AWSLC) +# define _Py_OPENSSL_CAN_SET_MEM_FUNCTIONS +#endif + +#ifdef _Py_OPENSSL_CAN_SET_MEM_FUNCTIONS + +static void * +_PyOpenSSL_Malloc(size_t size, const char *Py_UNUSED(file), + int Py_UNUSED(line)) +{ + return PyMem_RawMalloc(size); +} + +static void * +_PyOpenSSL_Realloc(void *ptr, size_t size, const char *Py_UNUSED(file), + int Py_UNUSED(line)) +{ + return PyMem_RawRealloc(ptr, size); +} + +static void +_PyOpenSSL_Free(void *ptr, const char *Py_UNUSED(file), + int Py_UNUSED(line)) +{ + PyMem_RawFree(ptr); +} + +#endif // _Py_OPENSSL_CAN_SET_MEM_FUNCTIONS + +static void +_PyOpenSSL_SetupMemFunctions(void) +{ +#ifdef _Py_OPENSSL_CAN_SET_MEM_FUNCTIONS + // Fails if OpenSSL has already allocated memory (e.g. another + // libcrypto user in the process); it then keeps its current allocator. + (void)CRYPTO_set_mem_functions(_PyOpenSSL_Malloc, _PyOpenSSL_Realloc, + _PyOpenSSL_Free); +#endif +} + +#endif // !Py_OPENSSL_MEM_H diff --git a/Modules/_ssl.c b/Modules/_ssl.c index aadc015405453f..73b32c1d86c72e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -41,6 +41,7 @@ #endif #include "_ssl.h" +#include "_openssl_mem.h" /* Redefined below for Windows debug builds after important #includes */ #define _PySSL_FIX_ERRNO @@ -7447,5 +7448,6 @@ static struct PyModuleDef _sslmodule_def = { PyMODINIT_FUNC PyInit__ssl(void) { + _PyOpenSSL_SetupMemFunctions(); return PyModuleDef_Init(&_sslmodule_def); } diff --git a/PCbuild/_hashlib.vcxproj b/PCbuild/_hashlib.vcxproj index 2cd205224bc089..e0110f32f201f1 100644 --- a/PCbuild/_hashlib.vcxproj +++ b/PCbuild/_hashlib.vcxproj @@ -97,6 +97,9 @@ ws2_32.lib;%(AdditionalDependencies) + + + diff --git a/PCbuild/_hashlib.vcxproj.filters b/PCbuild/_hashlib.vcxproj.filters index 7a0700c007f644..d26954116bb5ed 100644 --- a/PCbuild/_hashlib.vcxproj.filters +++ b/PCbuild/_hashlib.vcxproj.filters @@ -4,6 +4,9 @@ {cc45963d-bd25-4eb8-bdba-a5507090bca4} + + {5abcdd3e-a8bc-4833-949c-9477609092b9} + {67630fa4-76e4-4035-bced-043a6df1e2e0} @@ -13,6 +16,11 @@ Source Files + + + Header Files + + Resource Files diff --git a/PCbuild/_ssl.vcxproj b/PCbuild/_ssl.vcxproj index ce21f992ff8510..127b76fe11e329 100644 --- a/PCbuild/_ssl.vcxproj +++ b/PCbuild/_ssl.vcxproj @@ -97,6 +97,9 @@ ws2_32.lib;crypt32.lib;%(AdditionalDependencies) + + + diff --git a/PCbuild/_ssl.vcxproj.filters b/PCbuild/_ssl.vcxproj.filters index 8aef9e03fcc429..e96da0580e01b4 100644 --- a/PCbuild/_ssl.vcxproj.filters +++ b/PCbuild/_ssl.vcxproj.filters @@ -4,6 +4,9 @@ {695348f7-e9f6-4fe1-bc03-5f08ffc8095b} + + {7c1bd5da-8912-4107-b6ac-f3930f2d90c7} + {1b18a2e6-040d-46c7-a9ac-ac2ec64fb5d6} @@ -13,6 +16,11 @@ Source Files + + + Header Files + + Resource Files From 3c7e975001f0a96b63b33346fb1faab75d0035ee Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 2 Sep 2026 15:59:57 +0200 Subject: [PATCH 3/9] gh-156414: Document tomllib's limits & "extensions" (GH-156415) Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- Doc/library/tomllib.rst | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Doc/library/tomllib.rst b/Doc/library/tomllib.rst index 55610784362eb8..0a6b9d9d9fad4d 100644 --- a/Doc/library/tomllib.rst +++ b/Doc/library/tomllib.rst @@ -157,3 +157,48 @@ Conversion Table +------------------+--------------------------------------------------------------------------------------+ | array of tables | list of dicts | +------------------+--------------------------------------------------------------------------------------+ + +Limits and interoperability considerations +------------------------------------------ + +:mod:`!tomllib` places some limits on the documents it can handle, +and it preserves details that other TOML parsers are allowed to ignore. +When writing portable TOML files, only use features that are +guaranteed or recommended by the standard. + +The implementation details listed here may change in future versions of Python. + +Tables/dicts + The TOML spec does not guarantee key/value pairs in TOML documents and + tables to be in any specific order. + + .. impl-detail:: + :mod:`!tomllib` loads dictionary entries in the order they appear in + the source. + +Integers + TOML recommends supporting integers in ``range(−2**63, 2**63)``. + + .. impl-detail:: + :mod:`!tomllib` uses :ref:`Python's limit on integer string conversion + ` (4300 digits by default). + +Floats + TOML recommends supporting at least IEEE 754 binary64 values, + which means that numbers with more than 15 significant decimal digits + are likely to be rounded. + + .. impl-detail:: + :mod:`!tomllib` uses Python :class:`float` by default; + on many common platforms this is the recommended binary64. + See :data:`sys.float_info` for details. + +Nesting limit + TOML 1.1.0 does not recommend a limit on how deeply arrays and tables + may be nested inside one another. + (A limit of 100 has been proposed for a future version of TOML.) + + .. impl-detail:: + In :mod:`!tomllib`, the nesting level is mainly limited by Python's + :func:`recursion limit `. + Note that code that calls :mod:`!tomllib` may contribute to the limit. From 7d701ebb4c40c430a21d02277c7393c720ca798c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 2 Sep 2026 16:09:32 +0200 Subject: [PATCH 4/9] gh-141984: Move generator iterator reference out of syntax docs (GH-154884) Co-authored-by: Blaise Pabon Co-authored-by: Stan Ulbrych Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/stdtypes.rst | 293 ++++++++++++++++++++++++++++++++-- Doc/reference/expressions.rst | 173 -------------------- Doc/tools/removed-ids.txt | 14 ++ 3 files changed, 296 insertions(+), 184 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 31140555532f8f..ec37d29ef283ad 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -891,10 +891,12 @@ many numeric contexts, ``False`` and ``True`` behave like the integers 0 and 1, However, relying on this is discouraged; explicitly convert using :func:`int` instead. +.. _iterator-types: + .. _typeiter: -Iterator Types -============== +Iteration-related types +======================= .. index:: single: iterator protocol @@ -907,6 +909,9 @@ using two distinct methods; these are used to allow user-defined classes to support iteration. Sequences, described below in more detail, always support the iteration methods. +Iterables +--------- + One method needs to be defined for container objects to provide :term:`iterable` support: @@ -923,10 +928,14 @@ support: :c:member:`~PyTypeObject.tp_iter` slot of the type structure for Python objects in the Python/C API. +.. _stdtypes-iterators: + +Iterators +--------- + The iterator objects themselves are required to support the following two methods, which together form the :dfn:`iterator protocol`: - .. method:: iterator.__iter__() Return the :term:`iterator` object itself. This is required to allow both @@ -955,16 +964,278 @@ Implementations that do not obey this property are deemed broken. .. _generator-types: -Generator Types +Generator types --------------- -Python's :term:`generator`\s provide a convenient way to implement the iterator -protocol. If a container object's :meth:`~object.__iter__` method is implemented as a -generator, it will automatically return an iterator object (technically, a -generator object) supplying the :meth:`~iterator.__iter__` and :meth:`~generator.__next__` -methods. -More information about generators can be found in :ref:`the documentation for -the yield expression `. +Python's :term:`generators ` -- or more precisely, +:term:`generator functions ` and +:term:`generator iterators ` -- provide a convenient way +to implement the iterator protocol. + +A function that contains one or more :ref:`yield expressions ` +is a :term:`generator function`. +For example:: + + >>> def count_to_three(): + ... yield 0 + ... yield 1 + ... yield 2 + ... yield 3 + +Generator functions behave as regular +:ref:`user-defined functions ` +(for example, they have the same attributes), except that calling a generator +function returns a :ref:`generator iterator `:: + + >>> count_to_three() + + +Iterating a generator iterator executes code of the underlying +generator function, producing each :keyword:`yield`\ed value in turn:: + + >>> for number in count_to_three(): + ... print(number) + 0 + 1 + 2 + 3 + + >>> list(count_to_three()) + [0, 1, 2, 3] + +One common use for generator functions is implementing the +:meth:`~object.__iter__` method of custom iterable objects. +For example:: + + >>> class CardDeck: + ... def __iter__(self): + ... yield 'three of clubs' + ... yield 'ace of hearts' + + >>> list(CardDeck()) + ['three of clubs', 'ace of hearts'] + + +.. index:: pair: object; generator +.. _generator-methods: + +Generator iterators +^^^^^^^^^^^^^^^^^^^ + +Generator iterators implement the +:ref:`iterator protocol `. +Iterating them drives execution of the underlying generator function. + +.. index:: pair: exception; StopIteration + +.. method:: generator.__next__() + + Starts the execution of a generator function or resumes it at the + :ref:`yield expression ` where the function is currently suspended. + When a generator function is resumed with a :meth:`~generator.__next__` + method, the current yield expression always evaluates to :const:`None`. + The execution then continues to the next yield expression, where the + generator is suspended again, and the value of the expression after the + :keyword:`yield` keyword is returned to :meth:`~generator.__next__`'s + caller. + If the generator exits without yielding another value, + :meth:`~generator.__next__` raises a :exc:`StopIteration` exception, + signalling that iteration has completed. + + This method is normally called implicitly, for example by a :keyword:`for` + loop, or by the built-in :func:`next` function. + +Generator iterators have a few more methods than generic iterators, which +can be used to control the execution of the underlying generator function: + +.. method:: generator.send(value) + + "Sends" a value into the generator function: the *value* argument becomes + the result of the current yield expression. + + Otherwise, this method behaves like :meth:`~generator.__next__`: it resumes + the underlying function and either returns the next yielded value or raises + :exc:`StopIteration`. + + When :meth:`send` is called to start the generator, it must be called + with :const:`None` as the argument, because there is no current yield + expression that could receive the value. + + +.. method:: generator.throw(value) + generator.throw(type[, value[, traceback]]) + + Raises an exception at the point where the generator is currently suspended. + + Otherwise, this method behaves like :meth:`~generator.__next__`: it resumes + the underlying function and either returns the next yielded value or raises + :exc:`StopIteration`. + If the generator function does not catch the passed-in exception, or + raises a different exception, then that exception propagates to the caller. + + When :meth:`throw` is called to start the generator, the generator + immediately exits (that is, subsequent calls to :meth:`~generator.__next__` + will raise :exc:`StopIteration`) and the thrown exception is propagated to + :meth:`throw`'s caller. + + In typical use, this is called with a single argument, an exception instance, + similar to the way the :keyword:`raise` keyword is used. + + For backwards compatibility, however, the second signature is + supported, following a convention from older versions of Python. + The *type* argument should be an exception class, and *value* + should be an exception instance. If the *value* is not provided, the + *type* constructor is called to get an instance. If *traceback* + is provided, it is set on the exception, otherwise any existing + :attr:`~BaseException.__traceback__` attribute stored in *value* may + be cleared. + + .. versionchanged:: 3.12 + + The second signature \(type\[, value\[, traceback\]\]\) is deprecated and + may be removed in a future version of Python. + +.. index:: pair: exception; GeneratorExit + +.. method:: generator.close() + + Raises a :exc:`GeneratorExit` exception at the point where the generator + function is currently suspended (equivalent to calling ``throw(GeneratorExit)``). + + If the generator function has already exited (due to an exception or + normal return), or raises :exc:`GeneratorExit` (by not catching the + exception), :meth:`close` returns :const:`None`. + If the generator yields a value, a :exc:`RuntimeError` is raised. + If the generator raises any other exception, it is propagated to the caller. + If a generator returns a value upon being closed, that value is returned + by :meth:`close`. + + When a generator iterator is garbage collected before it has exited, + :meth:`~generator.close` is called automatically. + + .. versionchanged:: 3.13 + + If a generator returns a value upon being closed, the value is returned + by :meth:`close`. + Previously, it returned ``None``. + + +Calling any of the generator methods (:meth:`~generator.__next__`, +:meth:`~generator.send`, :meth:`~generator.throw`, :meth:`~generator.close`) +while one of these methods is already executing +raises a :exc:`ValueError` exception. + + +.. index:: pair: object; asynchronous-generator +.. _asynchronous-generator-methods: + +Asynchronous generator iterators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This subsection describes the methods of an asynchronous generator iterator, +which are used to control the execution of an asynchronous generator function. + + +.. index:: pair: exception; StopAsyncIteration + +.. method:: agen.__anext__() + :async: + + Returns an :term:`awaitable` which when run starts to execute the + asynchronous generator function or resumes it at the + :ref:`yield expression ` where the function is currently suspended. + When an asynchronous generator function is resumed with an + :meth:`~agen.__anext__` method, the current yield expression always + evaluates to :const:`None` in the returned awaitable, which when run will + continue to the next yield expression. + The value of the expression after the :keyword:`yield` keyword is the value + of the :exc:`StopIteration` exception raised by the completing coroutine. + If the asynchronous generator exits without yielding another value, the + awaitable instead raises a :exc:`StopAsyncIteration` exception, + signalling that the asynchronous iteration has completed. + + This method is normally called implicitly by an :keyword:`async for` loop, + or by the built-in :func:`anext` function. + + +Asynchronous generator-iterators have a few more methods than generic +asynchronous iterators, which can be used to control the execution of +the underlying generator function: + +.. method:: agen.asend(value) + :async: + + Returns an awaitable which, when run, "sends" a value into the underlying + asynchronous generator function: the *value* argument becomes + the result of the current yield expression. + + Otherwise, this method behaves like :meth:`~agen.__anext__`: when the + returned awaitable runs, it resumes the underlying function and either + returns the next yielded value as the value of the raised + :exc:`StopIteration`, or raises :exc:`StopAsyncIteration`. + + When :meth:`asend` is called to start the asynchronous + generator, it must be called with :const:`None` as the argument, + because there is no yield expression that could receive the value. + + +.. method:: agen.athrow(value) + agen.athrow(type[, value[, traceback]]) + :async: + + Returns an awaitable that, when run, raises an exception at the point where + the underlying asynchronous generator function is currently suspended. + + Otherwise, this method behaves like :meth:`~agen.__anext__`: when the + returned awaitable runs, it resumes the underlying function (with an + exception raised) and either returns the next yielded value as the value of + the raised :exc:`StopIteration`, or raises :exc:`StopAsyncIteration`. + If the underlying function does not catch the passed-in exception, or + raises a different exception, then when the awaitable is run, that + exception propagates to the caller of the awaitable. + + When :meth:`~agen.athrow` is called to start the generator, the generator + exits when the awaitable runs (that is, subsequent results from + :meth:`~agen.__anext__` will raise :exc:`StopAsyncIteration` when run) + and the thrown exception is propagated to the awaitable's caller. + + In typical use, this is called with a single argument, an exception instance, + similar to the way the :keyword:`raise` keyword is used. + + For backwards compatibility, however, the second signature is + supported. + An exception instance is created from three arguments in the same way as in + :meth:`generator.throw`. + + .. versionchanged:: 3.12 + + The second signature \(type\[, value\[, traceback\]\]\) is deprecated and + may be removed in a future version of Python. + + +.. index:: pair: exception; GeneratorExit + +.. method:: agen.aclose() + :async: + + Returns an awaitable that when run will throw a :exc:`GeneratorExit` into + the underlying asynchronous generator function at the point where it is + currently suspended (equivalent to calling ``athrow(GeneratorExit)``). + + If the asynchronous generator function then exits gracefully, is already + closed, or raises :exc:`GeneratorExit` (by not catching the exception), + then the returned awaitable will raise a :exc:`StopIteration` exception. + Any further awaitables returned by subsequent calls to the asynchronous + generator will raise a :exc:`StopAsyncIteration` exception. + + If the asynchronous generator yields a value, a :exc:`RuntimeError` is + raised by the awaitable. + If the asynchronous generator raises any other exception, that exception + is propagated to the caller of the awaitable. + + If the asynchronous generator has already exited due to an exception or + normal exit, then further calls to :meth:`aclose` will return an awaitable + that does nothing. .. _typesseq: diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index af313f42f9bff6..2e0b6621eb11c8 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1179,95 +1179,6 @@ on the right hand side of an assignment statement. The proposal that expanded on :pep:`492` by adding generator capabilities to coroutine functions. -.. index:: pair: object; generator -.. _generator-methods: - -Generator-iterator methods -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This subsection describes the methods of a generator iterator. They can -be used to control the execution of a generator function. - -Note that calling any of the generator methods below when the generator -is already executing raises a :exc:`ValueError` exception. - -.. index:: pair: exception; StopIteration - - -.. method:: generator.__next__() - - Starts the execution of a generator function or resumes it at the last - executed yield expression. When a generator function is resumed with a - :meth:`~generator.__next__` method, the current yield expression always - evaluates to :const:`None`. The execution then continues to the next yield - expression, where the generator is suspended again, and the value of the - :token:`~python-grammar:yield_list` is returned to :meth:`__next__`'s - caller. If the generator exits without yielding another value, a - :exc:`StopIteration` exception is raised. - - This method is normally called implicitly, e.g. by a :keyword:`for` loop, or - by the built-in :func:`next` function. - - -.. method:: generator.send(value) - - Resumes the execution and "sends" a value into the generator function. The - *value* argument becomes the result of the current yield expression. The - :meth:`send` method returns the next value yielded by the generator, or - raises :exc:`StopIteration` if the generator exits without yielding another - value. When :meth:`send` is called to start the generator, it must be called - with :const:`None` as the argument, because there is no yield expression that - could receive the value. - - -.. method:: generator.throw(value) - generator.throw(type[, value[, traceback]]) - - Raises an exception at the point where the generator was paused, - and returns the next value yielded by the generator function. If the generator - exits without yielding another value, a :exc:`StopIteration` exception is - raised. If the generator function does not catch the passed-in exception, or - raises a different exception, then that exception propagates to the caller. - - In typical use, this is called with a single exception instance similar to the - way the :keyword:`raise` keyword is used. - - For backwards compatibility, however, the second signature is - supported, following a convention from older versions of Python. - The *type* argument should be an exception class, and *value* - should be an exception instance. If the *value* is not provided, the - *type* constructor is called to get an instance. If *traceback* - is provided, it is set on the exception, otherwise any existing - :attr:`~BaseException.__traceback__` attribute stored in *value* may - be cleared. - - .. versionchanged:: 3.12 - - The second signature \(type\[, value\[, traceback\]\]\) is deprecated and - may be removed in a future version of Python. - -.. index:: pair: exception; GeneratorExit - - -.. method:: generator.close() - - Raises a :exc:`GeneratorExit` exception at the point where the generator - function was paused (equivalent to calling ``throw(GeneratorExit)``). - The exception is raised by the yield expression where the generator was paused. - If the generator function catches the exception and returns a - value, this value is returned from :meth:`close`. If the generator function - is already closed, or raises :exc:`GeneratorExit` (by not catching the - exception), :meth:`close` returns :const:`None`. If the generator yields a - value, a :exc:`RuntimeError` is raised. If the generator raises any other - exception, it is propagated to the caller. If the generator has already - exited due to an exception or normal exit, :meth:`close` returns - :const:`None` and has no other effect. - - .. versionchanged:: 3.13 - - If a generator returns a value upon being closed, the value is returned - by :meth:`close`. - .. index:: single: yield; examples Examples @@ -1367,90 +1278,6 @@ of a *finalizer* method see the implementation of The expression ``yield from `` is a syntax error when used in an asynchronous generator function. -.. index:: pair: object; asynchronous-generator -.. _asynchronous-generator-methods: - -Asynchronous generator-iterator methods -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This subsection describes the methods of an asynchronous generator iterator, -which are used to control the execution of a generator function. - - -.. index:: pair: exception; StopAsyncIteration - -.. method:: agen.__anext__() - :async: - - Returns an awaitable which when run starts to execute the asynchronous - generator or resumes it at the last executed yield expression. When an - asynchronous generator function is resumed with an :meth:`~agen.__anext__` - method, the current yield expression always evaluates to :const:`None` in the - returned awaitable, which when run will continue to the next yield - expression. The value of the :token:`~python-grammar:yield_list` of the - yield expression is the value of the :exc:`StopIteration` exception raised by - the completing coroutine. If the asynchronous generator exits without - yielding another value, the awaitable instead raises a - :exc:`StopAsyncIteration` exception, signalling that the asynchronous - iteration has completed. - - This method is normally called implicitly by a :keyword:`async for` loop. - - -.. method:: agen.asend(value) - :async: - - Returns an awaitable which when run resumes the execution of the - asynchronous generator. As with the :meth:`~generator.send` method for a - generator, this "sends" a value into the asynchronous generator function, - and the *value* argument becomes the result of the current yield expression. - The awaitable returned by the :meth:`asend` method will return the next - value yielded by the generator as the value of the raised - :exc:`StopIteration`, or raises :exc:`StopAsyncIteration` if the - asynchronous generator exits without yielding another value. When - :meth:`asend` is called to start the asynchronous - generator, it must be called with :const:`None` as the argument, - because there is no yield expression that could receive the value. - - -.. method:: agen.athrow(value) - agen.athrow(type[, value[, traceback]]) - :async: - - Returns an awaitable that raises an exception of type ``type`` at the point - where the asynchronous generator was paused, and returns the next value - yielded by the generator function as the value of the raised - :exc:`StopIteration` exception. If the asynchronous generator exits - without yielding another value, a :exc:`StopAsyncIteration` exception is - raised by the awaitable. - If the generator function does not catch the passed-in exception, or - raises a different exception, then when the awaitable is run that exception - propagates to the caller of the awaitable. - - .. versionchanged:: 3.12 - - The second signature \(type\[, value\[, traceback\]\]\) is deprecated and - may be removed in a future version of Python. - -.. index:: pair: exception; GeneratorExit - - -.. method:: agen.aclose() - :async: - - Returns an awaitable that when run will throw a :exc:`GeneratorExit` into - the asynchronous generator function at the point where it was paused. - If the asynchronous generator function then exits gracefully, is already - closed, or raises :exc:`GeneratorExit` (by not catching the exception), - then the returned awaitable will raise a :exc:`StopIteration` exception. - Any further awaitables returned by subsequent calls to the asynchronous - generator will raise a :exc:`StopAsyncIteration` exception. If the - asynchronous generator yields a value, a :exc:`RuntimeError` is raised - by the awaitable. If the asynchronous generator raises any other exception, - it is propagated to the caller of the awaitable. If the asynchronous - generator has already exited due to an exception or normal exit, then - further calls to :meth:`aclose` will return an awaitable that does nothing. - .. _primaries: Primaries diff --git a/Doc/tools/removed-ids.txt b/Doc/tools/removed-ids.txt index 059cbb9a846549..20bef00eb891cb 100644 --- a/Doc/tools/removed-ids.txt +++ b/Doc/tools/removed-ids.txt @@ -68,3 +68,17 @@ using/windows.html: return-codes using/windows.html: the-full-installer-deprecated using/windows.html: virtual-environments using/windows.html: windows-full + +# Moved to library/stdtypes: +reference/expressions.html: agen.__anext__ +reference/expressions.html: agen.aclose +reference/expressions.html: agen.asend +reference/expressions.html: agen.athrow +reference/expressions.html: asynchronous-generator-iterator-methods +reference/expressions.html: asynchronous-generator-methods +reference/expressions.html: generator-iterator-methods +reference/expressions.html: generator-methods +reference/expressions.html: generator.__next__ +reference/expressions.html: generator.close +reference/expressions.html: generator.send +reference/expressions.html: generator.throw From 2271fbb369a78fcd3ac00c526ca6711140144e43 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 2 Sep 2026 18:21:22 +0300 Subject: [PATCH 5/9] gh-153740: Refine test for _Float16: check support from libm (#156815) --- configure | 4 +++- configure.ac | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/configure b/configure index e0ad04b036aeb1..a2ca40de07d54e 100755 --- a/configure +++ b/configure @@ -16745,12 +16745,14 @@ else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ +#include int main(void) { _Float16 val = 1.0f16; + int test = isinf(val) || isnan(val); /* basic support from libm */ double d = 3.14; val = d; - return 0; + return test; } _ACEOF diff --git a/configure.ac b/configure.ac index 2b8a5b052878c9..e1333230fefa68 100644 --- a/configure.ac +++ b/configure.ac @@ -4474,12 +4474,14 @@ AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], WITH_SAVE_ENV([ CFLAGS="$CFLAGS -O0" AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include int main(void) { _Float16 val = 1.0f16; + int test = isinf(val) || isnan(val); /* basic support from libm */ double d = 3.14; val = d; - return 0; + return test; } ]])], [ac_cv_float16_supported=yes], [ac_cv_float16_supported=no], From d7f107a767e5768da4c7f9f5c4e112b12be101b1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 2 Sep 2026 17:57:31 +0200 Subject: [PATCH 6/9] gh-155742: Use PyBytesWriter in xml.etree (#155749) Replace soft deprecated PyBytes_FromStringAndSize() with PyBytesWriter. --- Modules/_elementtree.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index fa178f53e9b3ff..18bbbb618c2b18 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -3165,7 +3165,6 @@ makeuniversal(XMLParserObject* self, const char* string) necessary */ PyObject* tag; - char* p; Py_ssize_t i; /* look for namespace separator */ @@ -3174,22 +3173,28 @@ makeuniversal(XMLParserObject* self, const char* string) break; if (i != size) { /* convert to universal name */ - tag = PyBytes_FromStringAndSize(NULL, size+1); - if (tag == NULL) { + PyBytesWriter *writer = PyBytesWriter_Create(1 + size); + if (writer == NULL) { Py_DECREF(key); return NULL; } - p = PyBytes_AS_STRING(tag); + char *p = PyBytesWriter_GetData(writer); p[0] = '{'; memcpy(p+1, string, size); size++; + + tag = PyBytesWriter_Finish(writer); + if (tag == NULL) { + Py_DECREF(key); + return NULL; + } } else { /* plain name; use key as tag */ tag = Py_NewRef(key); } /* decode universal name */ - p = PyBytes_AS_STRING(tag); + const char *p = PyBytes_AS_STRING(tag); value = PyUnicode_DecodeUTF8(p, size, "strict"); Py_DECREF(tag); if (!value) { From 6f7941e0aac780d188a9970dbeeda98b2e3c9b88 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 2 Sep 2026 19:49:18 +0300 Subject: [PATCH 7/9] gh-156829: Fix inaccuracies in the xml.sax documentation (GH-156830) Corrected: * the name of the errorHandler argument of parse() and parseString(); * the default value of the parser_list argument of make_parser(); * the description of the source of parse(); * the types of the declaration-handler and dom-node properties; * the way in which names are interned; * the description of IncrementalParser. Documented: * default_parser_list and SAXReaderNotAvailable; * property_encoding and property_interning_dict; * IncrementalParser.prepareParser(); * XMLFilterBase.getParent() and setParent(); * which features and properties the parser based on Expat does not support. Added missing docstrings to parse(), parseString(), AttributesImpl and XMLGenerator. --- Doc/library/xml.sax.handler.rst | 44 +++++++++++++++++++++++++++++---- Doc/library/xml.sax.reader.rst | 9 +++++++ Doc/library/xml.sax.rst | 34 ++++++++++++++++++++----- Doc/library/xml.sax.utils.rst | 9 +++++++ Lib/xml/sax/__init__.py | 9 +++++++ Lib/xml/sax/saxutils.py | 1 + Lib/xml/sax/xmlreader.py | 5 ++-- 7 files changed, 98 insertions(+), 13 deletions(-) diff --git a/Doc/library/xml.sax.handler.rst b/Doc/library/xml.sax.handler.rst index 4188debf566d9b..07477f7d660128 100644 --- a/Doc/library/xml.sax.handler.rst +++ b/Doc/library/xml.sax.handler.rst @@ -72,12 +72,15 @@ for the feature and property names. optionally do not report original prefixed names (default). | access: (parsing) read-only; (not parsing) read/write + The parser based on :mod:`xml.parsers.expat` does not support this feature. + .. data:: feature_string_interning | value: ``"http://xml.org/sax/features/string-interning"`` | true: All element names, prefixes, attribute names, Namespace URIs, and - local names are interned using the built-in intern function. + local names are interned in a dictionary + (see :data:`property_interning_dict`). | false: Names are not necessarily interned, although they may be (default). | access: (parsing) read-only; (not parsing) read/write @@ -90,6 +93,9 @@ for the feature and property names. | false: Do not report validation errors. | access: (parsing) read-only; (not parsing) read/write + The parser based on :mod:`xml.parsers.expat` does not support this feature, + because Expat is a non-validating parser. + .. data:: feature_external_ges @@ -116,6 +122,8 @@ for the feature and property names. DTD subset. | access: (parsing) read-only; (not parsing) read/write + The parser based on :mod:`xml.parsers.expat` does not support this feature. + .. data:: all_features @@ -125,7 +133,7 @@ for the feature and property names. .. data:: property_lexical_handler | value: ``"http://xml.org/sax/properties/lexical-handler"`` - | data type: xml.sax.handler.LexicalHandler (not supported in Python 2) + | data type: :class:`~xml.sax.handler.LexicalHandler` | description: An optional extension handler for lexical events like comments. | access: read/write @@ -134,20 +142,25 @@ for the feature and property names. .. data:: property_declaration_handler | value: ``"http://xml.org/sax/properties/declaration-handler"`` - | data type: xml.sax.sax2lib.DeclHandler (not supported in Python 2) + | data type: an object implementing the SAX2 ``DeclHandler`` interface | description: An optional extension handler for DTD-related events other than notations and unparsed entities. | access: read/write + No parser in the standard library supports this property, + and the standard library provides no such handler. + .. data:: property_dom_node | value: ``"http://xml.org/sax/properties/dom-node"`` - | data type: org.w3c.dom.Node (not supported in Python 2) + | data type: :class:`xml.dom.Node` | description: When parsing, the current DOM node being visited if this is a DOM iterator; when not parsing, the root DOM node for iteration. | access: (parsing) read-only; (not parsing) read/write + No parser in the standard library supports this property. + .. data:: property_xml_string @@ -155,7 +168,28 @@ for the feature and property names. | data type: Bytes | description: The literal string of characters that was the source for the current event. - | access: read-only + | access: read-only, and only during a handler callback + + +.. data:: property_encoding + + | value: ``"http://www.python.org/sax/properties/encoding"`` + | data type: String + | description: The name of the encoding to assume for input data. + | access: read/write + + No parser in the standard library supports this property. + + +.. data:: property_interning_dict + + | value: ``"http://www.python.org/sax/properties/interning-dict"`` + | data type: Dictionary + | description: The dictionary used to intern names, + or ``None`` if names are not interned. + Setting it enables interning, as does the + :data:`feature_string_interning` feature. + | access: read/write .. data:: all_properties diff --git a/Doc/library/xml.sax.reader.rst b/Doc/library/xml.sax.reader.rst index 1a5ab6a214f819..ebbdb24364bfb8 100644 --- a/Doc/library/xml.sax.reader.rst +++ b/Doc/library/xml.sax.reader.rst @@ -216,6 +216,15 @@ Instances of :class:`IncrementalParser` offer the following additional methods: allocated during parsing. +.. method:: IncrementalParser.prepareParser(source) + + Prepare the parser for parsing *source*, an + :class:`InputSource` instance. + It is called by :meth:`~XMLReader.parse` before feeding the data. + The parser implementation must override this method; + the default implementation raises :exc:`NotImplementedError`. + + .. method:: IncrementalParser.reset() This method is called after close has been called to reset the parser so that it diff --git a/Doc/library/xml.sax.rst b/Doc/library/xml.sax.rst index 77234cac5d92ad..ab6eb061d35750 100644 --- a/Doc/library/xml.sax.rst +++ b/Doc/library/xml.sax.rst @@ -28,10 +28,10 @@ the SAX API. :meth:`~xml.sax.xmlreader.XMLReader.setFeature` on the parser object and argument :data:`~xml.sax.handler.feature_external_ges`. -The convenience functions are: +The convenience functions and data are: -.. function:: make_parser(parser_list=[]) +.. function:: make_parser(parser_list=()) Create and return a SAX :class:`~xml.sax.xmlreader.XMLReader` object. The first parser found will @@ -43,18 +43,23 @@ The convenience functions are: The *parser_list* argument can be any iterable, not just a list. -.. function:: parse(filename_or_stream, handler, error_handler=handler.ErrorHandler()) +.. function:: parse(filename_or_stream, handler, errorHandler=handler.ErrorHandler()) Create a SAX parser and use it to parse a document. The document, passed in as - *filename_or_stream*, can be a filename or a file object. The *handler* + *filename_or_stream*, can be a system identifier (a string identifying the + input source -- typically a file name or a URL), + a :term:`path-like ` object, or a file object. + A system identifier which does not refer to an existing file + is opened with :func:`urllib.request.urlopen`. + The *handler* parameter needs to be a SAX :class:`~handler.ContentHandler` instance. If - *error_handler* is given, it must be a SAX :class:`~handler.ErrorHandler` + *errorHandler* is given, it must be a SAX :class:`~handler.ErrorHandler` instance; if omitted, :exc:`SAXParseException` will be raised on all errors. There is no return value; all work must be done by the *handler* passed in. -.. function:: parseString(string, handler, error_handler=handler.ErrorHandler()) +.. function:: parseString(string, handler, errorHandler=handler.ErrorHandler()) Similar to :func:`parse`, but parses from a buffer *string* received as a parameter. *string* must be a :class:`str` instance or a @@ -63,6 +68,15 @@ The convenience functions are: .. versionchanged:: 3.5 Added support of :class:`str` instances. + +.. data:: default_parser_list + + The list of the names of modules which are tried by :func:`make_parser` + after the modules named in its *parser_list* argument. + It contains ``'xml.sax.expatreader'``, or, if the + :envvar:`!PY_SAX_PARSER` environment variable is set and the environment + is not ignored, the comma-separated list of module names taken from it. + A typical SAX application uses three kinds of objects: readers, handlers and input sources. "Reader" in this context is another term for parser, i.e. some piece of code that reads the bytes or characters from the input source, and @@ -135,6 +149,14 @@ classes. class for similar purposes. +.. exception:: SAXReaderNotAvailable(msg, exception=None) + + Subclass of :exc:`SAXNotSupportedException` raised when no parser is + available. A parser module raises it when it is imported or during + parsing if the parser it provides cannot be used, and :func:`make_parser` + raises it if no module from the tried ones provides a usable parser. + + .. seealso:: `SAX: The Simple API for XML `_ diff --git a/Doc/library/xml.sax.utils.rst b/Doc/library/xml.sax.utils.rst index 2de7ae2bda4260..4e58a8fc1c7be4 100644 --- a/Doc/library/xml.sax.utils.rst +++ b/Doc/library/xml.sax.utils.rst @@ -81,6 +81,15 @@ or as base classes. override specific methods to modify the event stream or the configuration requests as they pass through. + .. method:: getParent() + + Return the parent reader, or ``None`` if it is not set. + + + .. method:: setParent(parent) + + Set the parent reader, which the events are read from. + .. function:: prepare_input_source(source, base='') diff --git a/Lib/xml/sax/__init__.py b/Lib/xml/sax/__init__.py index fe4582c6f8b758..45f69c6bc223c5 100644 --- a/Lib/xml/sax/__init__.py +++ b/Lib/xml/sax/__init__.py @@ -27,12 +27,21 @@ def parse(source, handler, errorHandler=ErrorHandler()): + """Parse an XML document with the default parser. + + source is a system identifier, a path-like object or a file object, + handler is a ContentHandler instance, and errorHandler is an + ErrorHandler instance. All work is done by the handler.""" parser = make_parser() parser.setContentHandler(handler) parser.setErrorHandler(errorHandler) parser.parse(source) def parseString(string, handler, errorHandler=ErrorHandler()): + """Parse an XML document from a string with the default parser. + + string is a str or a bytes-like object, the other arguments are the + same as for parse().""" import io if errorHandler is None: errorHandler = ErrorHandler() diff --git a/Lib/xml/sax/saxutils.py b/Lib/xml/sax/saxutils.py index c1612ea1cebc5d..77ab9ee2faf038 100644 --- a/Lib/xml/sax/saxutils.py +++ b/Lib/xml/sax/saxutils.py @@ -110,6 +110,7 @@ def __getattr__(self, name): write_through=True) class XMLGenerator(handler.ContentHandler): + """Content handler which writes the events back as an XML document.""" def __init__(self, out=None, encoding="iso-8859-1", short_empty_elements=False): handler.ContentHandler.__init__(self) diff --git a/Lib/xml/sax/xmlreader.py b/Lib/xml/sax/xmlreader.py index e906121d23b9ef..98e93113fbb5ab 100644 --- a/Lib/xml/sax/xmlreader.py +++ b/Lib/xml/sax/xmlreader.py @@ -89,7 +89,7 @@ def setProperty(self, name, value): raise SAXNotRecognizedException("Property '%s' not recognized" % name) class IncrementalParser(XMLReader): - """This interface adds three extra methods to the XMLReader + """This interface adds four extra methods to the XMLReader interface that allow XML parsers to support incremental parsing. Support for this interface is optional, since not all underlying XML parsers support this functionality. @@ -104,7 +104,7 @@ class IncrementalParser(XMLReader): is, after parse has been called and before it returns. By default, the class also implements the parse method of the XMLReader - interface using the feed, close and reset methods of the + interface using the prepareParser, feed and close methods of the IncrementalParser interface as a convenience to SAX 2.0 driver writers.""" @@ -274,6 +274,7 @@ def getCharacterStream(self): # ===== ATTRIBUTESIMPL ===== class AttributesImpl: + """Implementation of the Attributes interface.""" def __init__(self, attrs): """Non-NS-aware implementation. From c2658214dafcfc95ec7f3eb7cc39c5011b3b8a0f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 2 Sep 2026 19:50:58 +0300 Subject: [PATCH 8/9] gh-156839: Fix inaccuracies in the xml.dom.pulldom documentation (GH-156840) Corrected: * the node types for the PROCESSING_INSTRUCTION and COMMENT events; * the return value of getEvent() at the end of the document; * the type of the argument of parseString(); * references to the xml.dom interfaces, written as xml.dom.minidom. Documented: * the event constants, which were referenced but never described; * PullDOM, SAX2DOM and the documentFactory argument; * the arguments of DOMEventStream, its reset() and clear() methods. Added docstrings to the module, the classes and their public methods, and removed the commented out paragraph about pulldom in the xml.dom documentation, which was never rendered and refers to a site which no longer exists. --- Doc/library/xml.dom.pulldom.rst | 98 ++++++++++++++++++++++++++------- Doc/library/xml.dom.rst | 8 --- Doc/tools/.nitignore | 1 - Lib/xml/dom/pulldom.py | 16 +++++- 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/Doc/library/xml.dom.pulldom.rst b/Doc/library/xml.dom.pulldom.rst index 52340ffe92eb85..85841d9d1951b8 100644 --- a/Doc/library/xml.dom.pulldom.rst +++ b/Doc/library/xml.dom.pulldom.rst @@ -6,6 +6,11 @@ **Source code:** :source:`Lib/xml/dom/pulldom.py` +.. The module was written by Paul Prescod and added in Python 2.0. + It is not based on any specification: the implementation is the only + reference. The Java Streaming API for XML (StAX, JSR 173) is based on + it, among other pull parsers. + -------------- The :mod:`!xml.dom.pulldom` module provides a "pull parser" which can also be @@ -48,19 +53,49 @@ Example:: doc.expandNode(node) print(node.toxml()) -``event`` is a constant and can be one of: +``event`` is one of the following constants, +and ``node`` is the node which the event is about. +The nodes implement the :mod:`xml.dom` interfaces; +they are created by the DOM implementation given to :class:`PullDOM`, +which is :mod:`xml.dom.minidom` by default. + + +.. data:: START_DOCUMENT + END_DOCUMENT + + The start and the end of the document. + *node* is the :class:`~xml.dom.Document`. + + +.. data:: START_ELEMENT + END_ELEMENT + + The start tag and the end tag of an element. + *node* is the :class:`~xml.dom.Element`. + + +.. data:: CHARACTERS + + Character data. + *node* is the :class:`~xml.dom.Text` node. + -* :data:`START_ELEMENT` -* :data:`END_ELEMENT` -* :data:`COMMENT` -* :data:`START_DOCUMENT` -* :data:`END_DOCUMENT` -* :data:`CHARACTERS` -* :data:`PROCESSING_INSTRUCTION` -* :data:`IGNORABLE_WHITESPACE` +.. data:: IGNORABLE_WHITESPACE -``node`` is an object of type :class:`xml.dom.minidom.Document`, -:class:`xml.dom.minidom.Element` or :class:`xml.dom.minidom.Text`. + White space in element content, as declared in the DTD. + *node* is the :class:`~xml.dom.Text` node. + + +.. data:: COMMENT + + A comment. + *node* is the :class:`~xml.dom.Comment` node. + + +.. data:: PROCESSING_INSTRUCTION + + A processing instruction. + *node* is the :class:`~xml.dom.ProcessingInstruction` node. Since the document is treated as a "flat" stream of events, the document "tree" is implicitly traversed and the desired elements are found regardless of their @@ -74,12 +109,19 @@ and switch to DOM-related processing. .. class:: PullDOM(documentFactory=None) - Subclass of :class:`xml.sax.handler.ContentHandler`. + Subclass of :class:`xml.sax.handler.ContentHandler` which turns SAX events + into the events of the pull parser. + The nodes are created, but they are not added to the tree, + unless :meth:`~DOMEventStream.expandNode` is called. + *documentFactory*, if given, is a DOM implementation used to create + the document; by default the implementation of :mod:`xml.dom.minidom` + is used. .. class:: SAX2DOM(documentFactory=None) - Subclass of :class:`xml.sax.handler.ContentHandler`. + Subclass of :class:`PullDOM` which also adds every created node + to the tree, so that the complete document is built. .. function:: parse(stream_or_string, parser=None, bufsize=None) @@ -95,7 +137,9 @@ If you have XML in a string, you can use the :func:`parseString` function instea .. function:: parseString(string, parser=None) - Return a :class:`DOMEventStream` that represents the (Unicode) *string*. + Return a :class:`DOMEventStream` that represents the *string*. + *string* must be a :class:`str` instance; + to parse bytes, pass a binary file object to :func:`parse`. .. data:: default_bufsize @@ -111,18 +155,21 @@ DOMEventStream Objects .. class:: DOMEventStream(stream, parser, bufsize) + Produce the events for the data read from the file object *stream* + by the :class:`~xml.sax.xmlreader.XMLReader` *parser*. + The data is read by *bufsize* bytes, or characters for a text stream, + at a time. + .. versionchanged:: 3.11 Support for :meth:`~object.__getitem__` method has been removed. .. method:: getEvent() - Return a tuple containing *event* and the current *node* as - :class:`xml.dom.minidom.Document` if event equals :data:`START_DOCUMENT`, - :class:`xml.dom.minidom.Element` if event equals :data:`START_ELEMENT` or - :data:`END_ELEMENT` or :class:`xml.dom.minidom.Text` if event equals - :data:`CHARACTERS`. + Return the next ``(event, node)`` tuple, + or ``None`` at the end of the document. + See above for the events and the corresponding nodes. The current node does not contain information about its children, unless - :func:`expandNode` is called. + :meth:`expandNode` is called. .. method:: expandNode(node) @@ -140,4 +187,13 @@ DOMEventStream Objects # Following statement prints node with all its children '

Some text

and more

' print(node.toxml()) - .. method:: DOMEventStream.reset() + .. method:: reset() + + Discard the events which are not read yet + and prepare the object for parsing a new document. + + + .. method:: clear() + + Release the parser and the document. + The stream is not closed, and the object can no longer be used. diff --git a/Doc/library/xml.dom.rst b/Doc/library/xml.dom.rst index 6f5904fdf35e80..b916e5ad9bd782 100644 --- a/Doc/library/xml.dom.rst +++ b/Doc/library/xml.dom.rst @@ -31,14 +31,6 @@ The Document Object Model is being defined by the W3C in stages, or "levels" in their terminology. The Python mapping of the API is substantially based on the DOM Level 2 recommendation. -.. What if your needs are somewhere between SAX and the DOM? Perhaps - you cannot afford to load the entire tree in memory but you find the - SAX model somewhat cumbersome and low-level. There is also a module - called xml.dom.pulldom that allows you to build trees of only the - parts of a document that you need structured access to. It also has - features that allow you to find your way around the DOM. - See http://www.prescod.net/python/pulldom - DOM applications typically start by parsing some XML into a DOM. How this is accomplished is not covered at all by DOM Level 1, and Level 2 provides only limited improvements: There is a :class:`DOMImplementation` object class which diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 4e7ec83723b344..3ce7c2f22e12b0 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -23,7 +23,6 @@ Doc/library/urllib.parse.rst Doc/library/urllib.request.rst Doc/library/wsgiref.rst Doc/library/xml.dom.minidom.rst -Doc/library/xml.dom.pulldom.rst Doc/library/xml.sax.reader.rst Doc/library/xml.sax.rst Doc/library/xmlrpc.client.rst diff --git a/Lib/xml/dom/pulldom.py b/Lib/xml/dom/pulldom.py index 913141cd7ef3ce..9d33d83458e39c 100644 --- a/Lib/xml/dom/pulldom.py +++ b/Lib/xml/dom/pulldom.py @@ -1,3 +1,5 @@ +"""Support for building partial DOM trees from SAX events.""" + import xml.sax import xml.sax.handler @@ -11,6 +13,10 @@ CHARACTERS = "CHARACTERS" class PullDOM(xml.sax.ContentHandler): + """Content handler which turns SAX events into pull parser events. + + The nodes are created, but they are not added to the tree.""" + _locator = None document = None @@ -202,6 +208,8 @@ def fatalError(self, exception): raise exception class DOMEventStream: + """Stream of the pull parser events.""" + def __init__(self, stream, parser, bufsize): self.stream = stream self.parser = parser @@ -211,6 +219,7 @@ def __init__(self, stream, parser, bufsize): self.reset() def reset(self): + """Discard unread events and prepare for parsing a new document.""" self.pulldom = PullDOM() # This content handler relies on namespace support self.parser.setFeature(xml.sax.handler.feature_namespaces, 1) @@ -226,6 +235,7 @@ def __iter__(self): return self def expandNode(self, node): + """Expand all children of the node into the node.""" event = self.getEvent() parents = [node] while event: @@ -241,6 +251,7 @@ def expandNode(self, node): event = self.getEvent() def getEvent(self): + """Return the next (event, node) tuple, or None at the end.""" # use IncrementalParser interface, so we get the desired # pull effect if not self.pulldom.firstEvent[1]: @@ -274,13 +285,14 @@ def _emit(self): return rc def clear(self): - """clear(): Explicitly release parsing objects""" + """Release the parser and the document.""" self.pulldom.clear() del self.pulldom self.parser = None self.stream = None class SAX2DOM(PullDOM): + """PullDOM which also adds every created node to the tree.""" def startElementNS(self, name, tagName , attrs): PullDOM.startElementNS(self, name, tagName, attrs) @@ -316,6 +328,7 @@ def characters(self, chars): default_bufsize = (2 ** 14) - 20 def parse(stream_or_string, parser=None, bufsize=None): + """Return a DOMEventStream for the given file name or file object.""" if bufsize is None: bufsize = default_bufsize if isinstance(stream_or_string, str): @@ -327,6 +340,7 @@ def parse(stream_or_string, parser=None, bufsize=None): return DOMEventStream(stream, parser, bufsize) def parseString(string, parser=None): + """Return a DOMEventStream for the given string.""" from io import StringIO bufsize = len(string) From 09ff4d443f0dc748bfa4d73c9bb6a22ae8a97993 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 2 Sep 2026 20:18:32 +0300 Subject: [PATCH 9/9] gh-156765: Fix inaccuracies in the ElementTree documentation (GH-156766) Corrected: * the parser argument of iterparse() is an instance of XMLParser or its subclass, not a subclass; * feed() accepts a string as well as encoded data; * keys() and items() do not return a list in the Python implementation, and the attributes are no longer returned in an arbitrary order. Removed the claims that the element tag, the attribute names and values, the text of a comment and the data of TreeBuilder.data() can be bytes. It is a Python 2 leftover. Documented instead what they can be. --- Doc/library/xml.etree.elementtree.rst | 49 ++++++++++++++++----------- Lib/xml/etree/ElementTree.py | 15 +++----- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index f9ffb07ada88fd..ec272a076c902b 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -551,10 +551,9 @@ Functions .. function:: Comment(text=None) Comment element factory. This factory function creates a special element - that will be serialized as an XML comment by the standard serializer. The - comment string can be either a bytestring or a Unicode string. *text* is a - string containing the comment string. Returns an element instance - representing a comment. + that will be serialized as an XML comment by the standard serializer. + *text* is a string containing the comment string. + Returns an element instance representing a comment. Note that :class:`XMLParser` skips over comments in the input instead of creating comment objects for them. An :class:`ElementTree` will @@ -621,10 +620,11 @@ Functions ``"pi"``, ``"start-ns"`` and ``"end-ns"`` (the "ns" events are used to get detailed namespace information). If *events* is omitted, only ``"end"`` events are reported. - *parser* is an optional parser instance. If not given, the standard - :class:`XMLParser` parser is used. *parser* must be a subclass of - :class:`XMLParser` and can only use the default :class:`TreeBuilder` as a - target. Returns an :term:`iterator` providing ``(event, elem)`` pairs; + *parser* is an optional parser instance. + If not given, the standard :class:`XMLParser` parser is used. + *parser* must be an instance of :class:`XMLParser` or its subclass + and can only use the default :class:`TreeBuilder` as a target. + Returns an :term:`iterator` providing ``(event, elem)`` pairs; it has a ``root`` attribute that references the root element of the resulting XML tree once *source* is fully read. The iterator has the :meth:`!close` method that closes the internal @@ -696,8 +696,7 @@ Functions Subelement factory. This function creates an element instance, and appends it to an existing element. - The element name, attribute names, and attribute values can be either - bytestrings or Unicode strings. *parent* is the parent element. *tag* is + *parent* is the parent element. *tag* is the subelement name. *attrib* is an optional dictionary, containing element attributes. *extra* contains additional attributes, given as keyword arguments. Returns an element instance. @@ -895,11 +894,22 @@ Element Objects Element class. This class defines the Element interface, and provides a reference implementation of this interface. - The element name, attribute names, and attribute values can be either - bytestrings or Unicode strings. *tag* is the element name. *attrib* is + *tag* is the element name. *attrib* is an optional dictionary, containing element attributes. *extra* contains additional attributes, given as keyword arguments. + The element name and the attribute names and values are strings or + :class:`QName` instances, and the text and the tail are strings or + ``None``. + The element name can also be :func:`Comment` or + :func:`ProcessingInstruction`, which are used for special elements. + If it is ``None``, the element itself is not serialized: only its text + and its children are written, and its attributes are ignored. + This can be used for a fragment which contains several elements. + With ``method="html"`` the attribute value can also be ``None``, + which produces an empty attribute (such as ``checked``). + Other objects can be stored in the tree, but they cannot be serialized. + .. versionchanged:: 3.15 *attrib* can now be a :class:`frozendict`. @@ -965,14 +975,12 @@ Element Objects .. method:: items() - Returns the element attributes as a sequence of (name, value) pairs. The - attributes are returned in an arbitrary order. + Returns the element attributes as (name, value) pairs. .. method:: keys() - Returns the elements attribute names as a list. The names are returned - in an arbitrary order. + Returns the element attribute names. .. method:: set(key, value) @@ -1313,8 +1321,7 @@ TreeBuilder Objects .. method:: data(data) - Adds text to the current element. *data* is a string. This should be - either a bytestring, or a Unicode string. + Adds text to the current element. *data* is a string. .. method:: end(tag) @@ -1417,7 +1424,8 @@ XMLParser Objects .. method:: feed(data) - Feeds data to the parser. *data* is encoded data. + Feeds data to the parser. *data* is a string + or encoded data (:class:`bytes` or a :term:`bytes-like object`). .. method:: flush() @@ -1496,7 +1504,8 @@ XMLPullParser Objects .. method:: feed(data) - Feed the given bytes data to the parser. + Feed the given data to the parser. *data* is a string + or encoded data (:class:`bytes` or a :term:`bytes-like object`). .. method:: flush() diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 2fee57df59fdd8..bed8c27df5a384 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -132,9 +132,6 @@ class Element: want to check if an element is truly empty, you should check BOTH its length AND its text attribute. - The element tag, attribute names, and attribute values can be either - bytes or strings. - *tag* is the element name. *attrib* is an optional dictionary containing element attributes. *extra* are additional element attributes given as keyword arguments. @@ -362,21 +359,17 @@ def set(self, key, value): self.attrib[key] = value def keys(self): - """Get list of attribute names. + """Get attribute names. - Names are returned in an arbitrary order, just like an ordinary - Python dict. Equivalent to attrib.keys() + Equivalent to attrib.keys() """ return self.attrib.keys() def items(self): - """Get element attributes as a sequence. - - The attributes are returned in arbitrary order. Equivalent to - attrib.items(). + """Get element attributes as (name, value) pairs. - Return a list of (name, value) tuples. + Equivalent to attrib.items(). """ return self.attrib.items()