diff --git a/Doc/library/concurrent.interpreters.rst b/Doc/library/concurrent.interpreters.rst index 24e9eaea5cad4b..2c509ab13a6acf 100644 --- a/Doc/library/concurrent.interpreters.rst +++ b/Doc/library/concurrent.interpreters.rst @@ -191,7 +191,7 @@ objects are either directly shared or copied efficiently. For example: * :class:`float` * :class:`tuple` (of similarly supported objects) -There is a small number of Python types that actually share mutable +There are a small number of Python types that actually share mutable data between interpreters: * :class:`memoryview` @@ -274,7 +274,7 @@ Interpreter objects .. method:: call(callable, /, *args, **kwargs) - Return the result of calling running the given function in the + Return the result of running the given function in the interpreter (in the current thread). .. _interp-call-in-thread: diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index f45ab397e93693..013150535cb089 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -65,14 +65,54 @@ are always available. They are listed here in alphabetical order. .. function:: aiter(async_iterable, /) + aiter(callable, /, stop_value, *, stop_exception=StopAsyncIteration) + aiter(callable, /, *, stop_exception) + + Return an :term:`asynchronous iterator` object. + The first argument is interpreted very differently + depending on the presence of the other arguments. + Without other arguments, + the single argument must be an :term:`asynchronous iterable`, + and the result is equivalent to calling ``x.__aiter__()``. + + If *stop_value* or *stop_exception* is given, + then the first argument must be a callable object. + The asynchronous iterator created in this case + calls *callable* with no arguments and awaits the result + for each call to its :meth:`~object.__anext__` method; + if the awaited value is equal to *stop_value*, + or if the call raises an exception matching *stop_exception*, + :exc:`StopAsyncIteration` will be raised, + otherwise the value will be returned. + The callable is only called when the result of :meth:`~object.__anext__` + is awaited. + + *stop_exception* is an exception class or a tuple of exception classes. + If *stop_value* is not specified, + the iteration stops only when the callable raises an exception. + If the callable raises :exc:`StopAsyncIteration` + which does not match *stop_exception*, + it is replaced with a :exc:`RuntimeError`, + as for asynchronous generators (see :pep:`525`). + + For example, reading fixed-size chunks from an asynchronous stream + until the end of file is reached:: - Return an :term:`asynchronous iterator` for an :term:`asynchronous iterable`. - Equivalent to calling ``x.__aiter__()``. + from functools import partial + async for chunk in aiter(partial(reader.read, 1024), b''): + process_chunk(chunk) + + Or consuming an :class:`asyncio.Queue` until it is shut down:: - Note: Unlike :func:`iter`, :func:`aiter` has no 2-argument variant. + from asyncio import QueueShutDown + async for item in aiter(queue.get, stop_exception=QueueShutDown): + process_item(item) .. versionadded:: 3.10 + .. versionchanged:: next + Added the *stop_value* and *stop_exception* parameters. + .. function:: all(iterable, /) Return ``True`` if all elements of the *iterable* are true (or if the iterable @@ -1143,22 +1183,34 @@ are always available. They are listed here in alphabetical order. .. function:: iter(iterable, /) - iter(callable, sentinel, /) + iter(callable, /, stop_value, *, stop_exception=StopIteration) + iter(callable, /, *, stop_exception) Return an :term:`iterator` object. The first argument is interpreted very - differently depending on the presence of the second argument. Without a - second argument, the single argument must be a collection object which supports the + differently depending on the presence of the other arguments. Without other + arguments, the single argument must be a collection object which supports the :term:`iterable` protocol (the :meth:`~object.__iter__` method), or it must support the sequence protocol (the :meth:`~object.__getitem__` method with integer arguments starting at ``0``). If it does not support either of those protocols, - :exc:`TypeError` is raised. If the second argument, *sentinel*, is given, + :exc:`TypeError` is raised. + + If *stop_value* or *stop_exception* is given, then the first argument must be a callable object. The iterator created in this case will call *callable* with no arguments for each call to its :meth:`~iterator.__next__` method; if the value returned is equal to - *sentinel*, :exc:`StopIteration` will be raised, otherwise the value will + *stop_value*, or if the call raises an exception matching *stop_exception*, + :exc:`StopIteration` will be raised, otherwise the value will be returned. + *stop_exception* is an exception class or a tuple of exception classes. + If *stop_value* is not specified, + the iteration stops only when the callable raises an exception. + If the callable raises :exc:`StopIteration` + which does not match *stop_exception*, + it is replaced with a :exc:`RuntimeError`, + as for generators (see :pep:`479`). + See also :ref:`typeiter`. One useful application of the second form of :func:`iter` is to build a @@ -1170,6 +1222,19 @@ are always available. They are listed here in alphabetical order. for block in iter(partial(f.read, 64), b''): process_block(block) + *stop_exception* is useful for callables + which report exhaustion by raising an exception + instead of returning a special value. + For example, draining a queue:: + + import queue + for item in iter(input_queue.get_nowait, stop_exception=queue.Empty): + process_item(item) + + .. versionchanged:: next + Added the *stop_exception* parameter + and allowed passing *stop_value* by keyword. + .. function:: len(object, /) diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index 310ccd651e18c7..f9ffb07ada88fd 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -711,16 +711,16 @@ Functions .. function:: tostring(element, encoding="us-ascii", method="xml", *, \ xml_declaration=None, default_namespace=None, \ - short_empty_elements=True) + short_empty_elements=True, standalone=None) Generates a string representation of an XML element, including all subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to generate a Unicode string (otherwise, a bytestring is generated). *method* is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``). - *xml_declaration*, *default_namespace* and *short_empty_elements* has the same - meaning as in :meth:`ElementTree.write`. Returns an (optionally) encoded string - containing the XML data. + *xml_declaration*, *default_namespace*, *short_empty_elements* and + *standalone* has the same meaning as in :meth:`ElementTree.write`. + Returns an (optionally) encoded string containing the XML data. .. versionchanged:: 3.4 Added the *short_empty_elements* parameter. @@ -732,19 +732,23 @@ Functions The :func:`tostring` function now preserves the attribute order specified by the user. + .. versionchanged:: next + Added the *standalone* parameter. + .. function:: tostringlist(element, encoding="us-ascii", method="xml", *, \ xml_declaration=None, default_namespace=None, \ - short_empty_elements=True) + short_empty_elements=True, standalone=None) Generates a string representation of an XML element, including all subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to generate a Unicode string (otherwise, a bytestring is generated). *method* is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``). - *xml_declaration*, *default_namespace* and *short_empty_elements* has the same - meaning as in :meth:`ElementTree.write`. Returns a list of (optionally) encoded - strings containing the XML data. It does not guarantee any specific sequence, + *xml_declaration*, *default_namespace*, *short_empty_elements* and + *standalone* has the same meaning as in :meth:`ElementTree.write`. + Returns a list of (optionally) encoded strings containing the XML data. + It does not guarantee any specific sequence, except that ``b"".join(tostringlist(element)) == tostring(element)``. .. versionadded:: 3.2 @@ -759,6 +763,9 @@ Functions The :func:`tostringlist` function now preserves the attribute order specified by the user. + .. versionchanged:: next + Added the *standalone* parameter. + .. function:: XML(text, parser=None) @@ -1186,7 +1193,7 @@ ElementTree Objects .. method:: write(file, encoding="us-ascii", xml_declaration=None, \ default_namespace=None, method="xml", *, \ - short_empty_elements=True) + short_empty_elements=True, standalone=None) Writes the element tree to a file, as XML. *file* is a file name, or a :term:`file object` opened for writing. *encoding* [1]_ is the output @@ -1202,6 +1209,13 @@ ElementTree Objects emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags. + The keyword-only *standalone* parameter is the value of the standalone + document declaration in the XML declaration. + Use ``True`` for ``standalone="yes"``, ``False`` for ``standalone="no"``, + and ``None`` (the default) to omit it. + An XML declaration is written if *standalone* is not ``None``; + combining it with ``xml_declaration=False`` raises a :exc:`ValueError`. + The output is either a string (:class:`str`) or binary (:class:`bytes`). This is controlled by the *encoding* argument. If *encoding* is ``"unicode"``, the output is a string; otherwise, it's binary. Note that @@ -1216,6 +1230,9 @@ ElementTree Objects The :meth:`write` method now preserves the attribute order specified by the user. + .. versionchanged:: next + Added the *standalone* parameter. + This is the XML file that is going to be manipulated:: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 4c432fb249f246..3262acd87d6d49 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -75,6 +75,13 @@ New features Other language changes ====================== +* The :func:`iter` function now accepts the *stop_exception* parameter. + The created iterator stops when the callable raises the specified exception. + The second parameter is now named *stop_value* and can be passed by keyword. + :func:`aiter` now accepts the same *stop_value* and *stop_exception* + parameters, calling an asynchronous callable and awaiting the result. + (Contributed by Serhiy Storchaka in :gh:`64862`.) + * :meth:`memoryview.cast` now allows casting a multidimensional F-contiguous view to a one-dimensional view. (Contributed by Jaemin Park in :gh:`91484`.) diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index c86ae242feac1e..266add0fb7a9c3 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -29,6 +29,9 @@ PyAPI_FUNC(int) _PyGen_SetStopIterationValue(PyObject *); // Export for '_asyncio' shared extension PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **); +// Set the exception passed to throw(typ[, val[, tb]]). +// Return 0 on success, -1 on failure. +extern int _PyGen_SetException(PyObject *typ, PyObject *val, PyObject *tb); PyAPI_FUNC(PyObject *)_PyCoro_GetAwaitableIter(PyObject *o); PyAPI_FUNC(PyObject *)_PyAsyncGenValueWrapperNew(PyThreadState *state, PyObject *); diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 9ab20be70614de..4fd6c618fb7440 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -2108,6 +2108,8 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdout)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(step)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(steps)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stop_exception)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stop_value)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(store_name)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strategy)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strftime)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 51d9fbe89b3423..5b35c53e0aa03b 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -831,6 +831,8 @@ struct _Py_global_strings { STRUCT_FOR_ID(stdout) STRUCT_FOR_ID(step) STRUCT_FOR_ID(steps) + STRUCT_FOR_ID(stop_exception) + STRUCT_FOR_ID(stop_value) STRUCT_FOR_ID(store_name) STRUCT_FOR_ID(strategy) STRUCT_FOR_ID(strftime) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 0623adce693d46..6c907e0cf79894 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -538,7 +538,7 @@ struct _py_func_state { If you add a new static type to the standard library, you may have to update one of these numbers. */ -#define _Py_NUM_MANAGED_PREINITIALIZED_TYPES 120 +#define _Py_NUM_MANAGED_PREINITIALIZED_TYPES 122 #define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES \ (_Py_NUM_MANAGED_PREINITIALIZED_TYPES + 83) #define _Py_MAX_MANAGED_STATIC_EXT_TYPES 10 diff --git a/Include/internal/pycore_iterobject.h b/Include/internal/pycore_iterobject.h new file mode 100644 index 00000000000000..90b444976e2f19 --- /dev/null +++ b/Include/internal/pycore_iterobject.h @@ -0,0 +1,28 @@ +#ifndef Py_INTERNAL_ITEROBJECT_H +#define Py_INTERNAL_ITEROBJECT_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +extern PyTypeObject _PyACallIter_Type; +extern PyTypeObject _PyACallIterAwaitable_Type; + +// Like PyCallIter_New(), but the iteration also stops when *callable* raises +// an exception matching *stop_exc* (an exception class or a tuple of exception +// classes). *sentinel* can be NULL; NULL *stop_exc* means StopIteration. +extern PyObject *_PyCallIter_NewEx(PyObject *callable, PyObject *sentinel, + PyObject *stop_exc); + +// The asynchronous counterpart of _PyCallIter_NewEx(): the result of +// *callable* is awaited, and NULL *stop_exc* means StopAsyncIteration. +extern PyObject *_PyACallIter_New(PyObject *callable, PyObject *sentinel, + PyObject *stop_exc); + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_ITEROBJECT_H */ diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 88ca09e6ba245f..c80925f020186b 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -2106,6 +2106,8 @@ extern "C" { INIT_ID(stdout), \ INIT_ID(step), \ INIT_ID(steps), \ + INIT_ID(stop_exception), \ + INIT_ID(stop_value), \ INIT_ID(store_name), \ INIT_ID(strategy), \ INIT_ID(strftime), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 3c4d7d664537a8..b30cfc678de1cd 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -3104,6 +3104,14 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(stop_exception); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(stop_value); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(store_name); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 70a285dd91f385..cdae58b3e89ae3 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -789,6 +789,164 @@ async def gen(): applied_twice = aiter(applied_once) self.assertIs(applied_once, applied_twice) + def make_counter(self): + state = {'n': 0} + async def counter(): + state['n'] += 1 + return state['n'] + return counter + + def collect(self, ait): + async def consume(): + return [i async for i in ait] + return self.loop.run_until_complete(consume()) + + def test_aiter_callable_stop(self): + self.assertEqual(self.collect(aiter(self.make_counter(), 4)), [1, 2, 3]) + self.assertEqual(self.collect(aiter(self.make_counter(), stop_value=4)), + [1, 2, 3]) + + def test_aiter_callable_stop_exception(self): + counter = self.make_counter() + async def spam(): + value = await counter() + if value > 3: + raise LookupError + return value + self.assertEqual(self.collect(aiter(spam, stop_exception=LookupError)), + [1, 2, 3]) + counter = self.make_counter() + self.assertEqual( + self.collect(aiter(spam, stop_exception=(ZeroDivisionError, + LookupError))), + [1, 2, 3]) + + def test_aiter_callable_stop_and_exception(self): + counter = self.make_counter() + async def spam(): + value = await counter() + if value > 5: + raise LookupError + return value + self.assertEqual( + self.collect(aiter(spam, 3, stop_exception=LookupError)), [1, 2]) + counter = self.make_counter() + self.assertEqual( + self.collect(aiter(spam, 100, stop_exception=LookupError)), + [1, 2, 3, 4, 5]) + + def test_aiter_callable_stop_async_iteration(self): + # StopAsyncIteration is the default stop exception + counter = self.make_counter() + async def spam(): + value = await counter() + if value > 3: + raise StopAsyncIteration + return value + self.assertEqual( + self.collect(aiter(spam, stop_exception=StopAsyncIteration)), + [1, 2, 3]) + + def test_aiter_callable_leak_from_await(self): + # A StopAsyncIteration leaking from the await is replaced with + # RuntimeError (see PEP 525) + async def spam(): + raise StopAsyncIteration + it = aiter(spam, 10, stop_exception=LookupError) + with self.assertRaisesRegex(RuntimeError, + 'callable raised StopAsyncIteration') as cm: + self.loop.run_until_complete(anext(it)) + self.assertIsInstance(cm.exception.__cause__, StopAsyncIteration) + # but if it matches stop_exception, it stops the iteration + it = aiter(spam, 10, stop_exception=(LookupError, StopAsyncIteration)) + with self.assertRaises(StopAsyncIteration): + self.loop.run_until_complete(anext(it)) + + def test_aiter_callable_leak_from_call(self): + # StopIteration and StopAsyncIteration leaking from the call are + # replaced with RuntimeError (see PEP 525) + for exc in StopIteration, StopAsyncIteration: + with self.subTest(exc=exc): + def spam(): + raise exc + it = aiter(spam, 10, stop_exception=LookupError) + with self.assertRaisesRegex( + RuntimeError, f'callable raised {exc.__name__}') as cm: + self.loop.run_until_complete(anext(it)) + self.assertIsInstance(cm.exception.__cause__, exc) + # but if it matches stop_exception, it stops the iteration + it = aiter(spam, 10, stop_exception=(LookupError, exc)) + with self.assertRaises(StopAsyncIteration): + self.loop.run_until_complete(anext(it)) + + def test_aiter_callable_other_exception(self): + async def spam(): + raise ZeroDivisionError + it = aiter(spam, stop_exception=LookupError) + with self.assertRaises(ZeroDivisionError): + self.loop.run_until_complete(anext(it)) + + def test_aiter_callable_exhausted(self): + it = aiter(self.make_counter(), 3) + self.assertEqual(self.collect(it), [1, 2]) + self.assertEqual(self.loop.run_until_complete(anext(it, 'default')), + 'default') + with self.assertRaises(StopAsyncIteration): + self.loop.run_until_complete(anext(it)) + + def test_aiter_callable_lazy(self): + # The callable is only called when the awaitable is awaited + calls = [] + async def spam(): + calls.append(1) + return len(calls) + it = aiter(spam, 10) + awaitable = it.__anext__() + self.assertEqual(calls, []) + self.assertEqual(self.loop.run_until_complete(awaitable), 1) + self.assertEqual(calls, [1]) + + def test_aiter_callable_awaitable(self): + it = aiter(self.make_counter(), 10) + awaitable = it.__anext__() + self.assertIsNone(awaitable.close()) + with self.assertRaises(RuntimeError): + self.loop.run_until_complete(awaitable) + awaitable = it.__anext__() + with self.assertRaises(KeyError): + awaitable.throw(KeyError('injected')) + + def test_aiter_callable_cancel(self): + # Cancellation is delivered to the awaited callable result + cancelled = [] + async def spam(): + try: + await asyncio.sleep(10) + except asyncio.CancelledError: + cancelled.append(1) + raise + async def consume(): + async for _ in aiter(spam, None): + pass + async def main(): + task = asyncio.ensure_future(consume()) + await asyncio.sleep(0) + task.cancel() + with self.assertRaises(asyncio.CancelledError): + await task + self.loop.run_until_complete(main()) + self.assertEqual(cancelled, [1]) + + def test_aiter_callable_errors(self): + async def gen(): + yield 1 + self.assertRaises(TypeError, aiter, gen(), 1) + self.assertRaises(TypeError, aiter, [1, 2], stop_exception=LookupError) + self.assertRaises(TypeError, aiter, len, stop_exception=42) + self.assertRaises(TypeError, aiter, len, + stop_exception=(LookupError, 42)) + self.assertRaises(TypeError, aiter, len, stop_exception=LookupError()) + def test_anext_bad_args(self): async def gen(): yield 1 diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 8b25607b6d504f..91da2cf5ee9f61 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -226,6 +226,8 @@ def test_pack_unpack_roundtrip_for_nans(self): value = unpack(data1, endian) data2 = pack(size, value, endian) self.assertTrue(math.isnan(value)) + self.assertEqual(math.copysign(1.0, value), + -1.0 if sign else 1.0) self.assertEqual(data1, data2) @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 433afac875aa7b..b4585adb07ece4 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -342,5 +342,20 @@ def test_pyobject_dump(self): self.assertRegex(output, r'') +class RefTracerTest(unittest.TestCase): + @support.requires_resource('cpu') + @support.skip_emscripten_stack_overflow() + @support.skip_wasi_stack_overflow() + def test_destroy_traced_for_trashcan_deferred_objects(self): + depth = 200_000 + chain = None + for _ in range(depth): + chain = [chain] + _testcapi.start_counting_list_destroys() + del chain + destroys = _testcapi.stop_counting_list_destroys() + self.assertEqual(destroys, depth) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 8930f6343ac299..df5843abfcb875 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6171,10 +6171,10 @@ def test_builtins_have_signatures(self): 'dict', 'frozendict', 'int', 'str'} # These need PEP 457 groups needs_groups = {"range", "slice", "dir", "getattr", - "next", "iter", "vars"} + "next", "vars"} no_signature |= needs_groups # These have unrepresentable parameter default values of NULL - unsupported_signature = {"anext"} + unsupported_signature = {"anext", "aiter", "iter"} # These need *args support in Argument Clinic needs_varargs = {"min", "max", "__build_class__"} no_signature |= needs_varargs diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py index 18e4b676c53236..be9d0a709f2f4a 100644 --- a/Lib/test/test_iter.py +++ b/Lib/test/test_iter.py @@ -93,7 +93,7 @@ def __call__(self): i = self.i self.i = i + 1 if i > 100: - raise IndexError # Emergency stop + raise IndexError # stops the iteration return i class EmptyIterClass: @@ -350,6 +350,97 @@ def spam(state=[0]): return i self.check_iterator(iter(spam, 20), list(range(10)), pickle=False) + # Test iter() with the stop value passed by keyword + def test_iter_keyword_stop(self): + self.check_iterator(iter(CallableIterClass(), stop_value=10), list(range(10))) + + # Test iter() with the exception argument + def test_iter_exception(self): + self.check_iterator(iter(CallableIterClass(), stop_exception=IndexError), + list(range(101))) + + def test_iter_exception_tuple(self): + self.check_iterator( + iter(CallableIterClass(), stop_exception=(ZeroDivisionError, IndexError)), + list(range(101))) + + # Test iter() with both the stop value and the exception argument + def test_iter_exception_and_stop(self): + self.check_iterator(iter(CallableIterClass(), 10, stop_exception=IndexError), + list(range(10))) + self.check_iterator(iter(CallableIterClass(), 200, stop_exception=IndexError), + list(range(101))) + + # A leaking StopIteration is replaced with RuntimeError (see PEP 479) + def test_iter_exception_stop_iteration_leak(self): + def spam(): + raise StopIteration + it = iter(spam, stop_exception=IndexError) + with self.assertRaisesRegex(RuntimeError, + 'callable raised StopIteration') as cm: + next(it) + self.assertIsInstance(cm.exception.__cause__, StopIteration) + # but if it matches stop_exception, it stops the iteration + it = iter(spam, stop_exception=(IndexError, StopIteration)) + self.assertRaises(StopIteration, next, it) + + # Other exceptions are propagated + def test_iter_exception_not_matching(self): + def spam(): + raise ZeroDivisionError + it = iter(spam, stop_exception=IndexError) + self.assertRaises(ZeroDivisionError, next, it) + + def test_iter_exception_errors(self): + self.assertRaises(TypeError, iter, [1, 2], stop_exception=IndexError) + self.assertRaises(TypeError, iter, len, stop_exception=42) + self.assertRaises(TypeError, iter, len, stop_exception=(IndexError, 42)) + self.assertRaises(TypeError, iter, len, stop_exception=IndexError()) + + # StopIteration is the default stop exception + def test_iter_exception_stop_iteration(self): + def spam(state=[0]): + i = state[0] + if i == 10: + raise StopIteration + state[0] = i+1 + return i + self.check_iterator(iter(spam, stop_exception=StopIteration), + list(range(10)), pickle=False) + + def test_calliter_reduce(self): + c = CallableIterClass() + # The form without the stop exception is pickled as iter(c, stop) + self.assertEqual(iter(c, 10).__reduce__(), (iter, (c, 10))) + self.assertEqual(iter(c, 10, stop_exception=StopIteration).__reduce__(), + (iter, (c, 10))) + self.assertEqual(iter(c, 10, stop_exception=()).__reduce__(), + (iter, (c, None), ((10,), ()))) + self.assertEqual(iter(c, stop_exception=StopIteration).__reduce__(), + (iter, (c, None), ((), StopIteration))) + self.assertEqual(iter(c, stop_exception=IndexError).__reduce__(), + (iter, (c, None), ((), IndexError))) + self.assertEqual(iter(c, 10, stop_exception=IndexError).__reduce__(), + (iter, (c, None), ((10,), IndexError))) + + def test_calliter_setstate(self): + c = CallableIterClass() + it = iter(c, stop_exception=IndexError) + self.assertRaises(TypeError, it.__setstate__, 42) + self.assertRaises(TypeError, it.__setstate__, ((), IndexError, ())) + self.assertRaises(TypeError, it.__setstate__, ([], IndexError)) + self.assertRaises(TypeError, it.__setstate__, ((1, 2), IndexError)) + self.assertRaises(TypeError, it.__setstate__, ((), 42)) + self.assertRaises(TypeError, it.__setstate__, ((), None)) + it.__setstate__(((10,), StopIteration)) + self.assertEqual(it.__reduce__(), (iter, (c, 10))) + it.__setstate__(((10,), ())) + self.assertEqual(it.__reduce__(), (iter, (c, None), ((10,), ()))) + it.__setstate__(((), IndexError)) + self.assertEqual(it.__reduce__(), (iter, (c, None), ((), IndexError))) + it.__setstate__(((10,), StopIteration)) + self.assertEqual(list(it), list(range(10))) + def test_iter_function_concealing_reentrant_exhaustion(self): # gh-101892: Test two-argument iter() with a function that # exhausts its associated iterator but forgets to either return diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index f2adce532595e7..4308de227a46ce 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1726,7 +1726,7 @@ def get_gen(): yield 1 check(iter('abc'), size('lP')) # callable-iterator import re - check(re.finditer('',''), size('2P')) + check(re.finditer('',''), size('3P')) # list check(list([]), vsize('Pn')) check(list([1]), vsize('Pn') + 2*self.P) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index fb35bb6a5f442f..a0337de58b23ae 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -970,6 +970,88 @@ def test_tostring_xml_declaration_cases(self): expected_retval ) + def test_tostring_standalone(self): + elem = ET.XML('') + self.assertEqual( + ET.tostring(elem, encoding='unicode', standalone=True), + "\n" + "" + ) + self.assertEqual( + ET.tostring(elem, encoding='unicode', standalone=False), + "\n" + "" + ) + # the XML declaration is written even if it would be omitted + self.assertEqual( + ET.tostring(elem, standalone=True), + b"\n" + b"" + ) + self.assertEqual( + ET.tostring(elem, encoding='UTF-8', standalone=False), + b"\n" + b"" + ) + + def test_tostring_standalone_none(self): + elem = ET.XML('') + self.assertEqual( + ET.tostring(elem, encoding='unicode', standalone=None), + '' + ) + self.assertEqual( + ET.tostring(elem, encoding='unicode', xml_declaration=True, + standalone=None), + "\n" + ) + + def test_tostring_standalone_without_xml_declaration(self): + elem = ET.XML('') + for standalone in True, False: + for xml_declaration in False, 0, '': + with self.subTest(standalone=standalone, + xml_declaration=xml_declaration): + with self.assertRaises(ValueError): + ET.tostring(elem, xml_declaration=xml_declaration, + standalone=standalone) + + def test_tostring_standalone_text_method(self): + elem = ET.XML('text') + self.assertEqual( + ET.tostring(elem, encoding='unicode', method='text', + standalone=True), + 'text' + ) + + def test_tostringlist_standalone(self): + elem = ET.XML('') + self.assertEqual( + b''.join(ET.tostringlist(elem, standalone=True)), + b"\n" + b"" + ) + with self.assertRaises(ValueError): + ET.tostringlist(elem, xml_declaration=False, standalone=False) + + def test_write_standalone(self): + elem = ET.XML('') + tree = ET.ElementTree(elem) + for standalone, expected in [ + (True, "standalone='yes'"), (False, "standalone='no'")]: + with self.subTest(standalone=standalone): + file = io.StringIO() + tree.write(file, encoding='unicode', standalone=standalone) + self.assertEqual( + file.getvalue(), + "\n" + "" % expected + ) + file = io.StringIO() + with self.assertRaises(ValueError): + tree.write(file, encoding='unicode', xml_declaration=False, + standalone=True) + def test_tostringlist_default_namespace(self): elem = ET.XML('') self.assertEqual( @@ -1096,6 +1178,41 @@ def test_parse_text_source_multiple_chunks(self): xml = "%s" % body self.assertEqual(ET.parse(io.StringIO(xml)).getroot().text, body) + def test_parse_input_larger_than_chunk(self): + # gh-83895: the C implementation feeds Expat in chunks of 1 MiB + size = 3 * (1 << 20) + xml = '%s' % ('x' * size) + for source in xml, xml.encode(): + with self.subTest(type=type(source).__name__): + root = ET.fromstring(source) + self.assertEqual(len(root[0].text), size) + self.assertEqual(root[1].tag, 'b') + + # gh-83895: input larger than INT_MAX is fed to Expat in chunks. + # memuse is 3 for the Python implementation, which joins the collected + # data, 2 would be enough for the C implementation. + @support.bigmemtest(size=support._2G + 100, memuse=3, dry_run=False) + def test_large_input(self, size): + data = b'' + b'x' * size + b'' + root = None + try: + parser = ET.XMLParser() + parser.feed(data) + data = None + root = parser.close() + self.assertEqual(len(root.text), size) + finally: + data = None + root = None + + def test_parse_error_after_chunk_boundary(self): + # the reported position accounts for the preceding chunks + size = 2 * (1 << 20) + with self.assertRaises(ET.ParseError) as cm: + ET.fromstring('%s<' % ('x' * size)) + self.assertEqual(cm.exception.position, (1, size + 4)) + + @support.subTests('sample,exception', [ (b' \xa1', UnicodeDecodeError), # crashed (b' \xa1\n" % ( - declared_encoding,)) + if standalone is None: + sddecl = "" + else: + sddecl = " standalone='%s'" % ( + "yes" if standalone else "no",) + write("\n" % ( + declared_encoding, sddecl)) if method == "text": _serialize_text(write, self._root) else: @@ -1085,7 +1100,7 @@ def _escape_attrib_html(text): def tostring(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, - short_empty_elements=True): + short_empty_elements=True, standalone=None): """Generate string representation of XML element. All subelements are included. If encoding is "unicode", a string @@ -1094,7 +1109,9 @@ def tostring(element, encoding=None, method=None, *, *element* is an Element instance, *encoding* is an optional output encoding defaulting to US-ASCII, *method* is an optional output which can be one of "xml" (default), "html" or "text", - *default_namespace* sets the default XML namespace (for "xmlns"). + *default_namespace* sets the default XML namespace (for "xmlns"), + *standalone* is the value of the standalone document declaration + in the XML declaration (omitted if None). Returns an (optionally) encoded string containing the XML data. @@ -1104,7 +1121,8 @@ def tostring(element, encoding=None, method=None, *, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, - short_empty_elements=short_empty_elements) + short_empty_elements=short_empty_elements, + standalone=standalone) return stream.getvalue() class _ListDataStream(io.BufferedIOBase): @@ -1126,14 +1144,15 @@ def tell(self): def tostringlist(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, - short_empty_elements=True): + short_empty_elements=True, standalone=None): lst = [] stream = _ListDataStream(lst) ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, - short_empty_elements=short_empty_elements) + short_empty_elements=short_empty_elements, + standalone=standalone) return lst diff --git a/Makefile.pre.in b/Makefile.pre.in index adcfe4c5259eb2..b2bd89039e1230 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1361,6 +1361,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_interpframe_structs.h \ $(srcdir)/Include/internal/pycore_interpolation.h \ $(srcdir)/Include/internal/pycore_intrinsics.h \ + $(srcdir)/Include/internal/pycore_iterobject.h \ $(srcdir)/Include/internal/pycore_jit.h \ $(srcdir)/Include/internal/pycore_lazyimportobject.h \ $(srcdir)/Include/internal/pycore_list.h \ diff --git a/Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst b/Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst new file mode 100644 index 00000000000000..f01009f10e9b5b --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst @@ -0,0 +1,3 @@ +If available on the platform, use native :c:type:`_Float16` in +:c:func:`PyFloat_Pack2` and :c:func:`PyFloat_Unpack2` functions. Patch by +Sergey B Kirpichev. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-23-21-30-00.gh-issue-64862.Kx7Qm2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-23-21-30-00.gh-issue-64862.Kx7Qm2.rst new file mode 100644 index 00000000000000..61a42300994aa8 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-23-21-30-00.gh-issue-64862.Kx7Qm2.rst @@ -0,0 +1,5 @@ +The :func:`iter` function now accepts the *stop_exception* parameter. +The created iterator stops when the callable raises the specified exception. +The second parameter is now named *stop_value* and can be passed by keyword. +:func:`aiter` now accepts the same *stop_value* and *stop_exception* +parameters, calling an asynchronous callable and awaiting the result. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst new file mode 100644 index 00000000000000..31ebff6f247f7e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst @@ -0,0 +1,2 @@ +Fix missing ``PyRefTracer_DESTROY`` events for trashcan deferred objects. +Patch by Donghee Na. diff --git a/Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst b/Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst new file mode 100644 index 00000000000000..d0b8ead7506af0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst @@ -0,0 +1,5 @@ +:meth:`ElementTree.write() `, +:func:`~xml.etree.ElementTree.tostring` and +:func:`~xml.etree.ElementTree.tostringlist` now support the *standalone* +parameter, the value of the standalone document declaration +in the XML declaration. diff --git a/Misc/NEWS.d/next/Library/2026-08-31-23-10-00.gh-issue-83895.Jm5tR3.rst b/Misc/NEWS.d/next/Library/2026-08-31-23-10-00.gh-issue-83895.Jm5tR3.rst new file mode 100644 index 00000000000000..c6e2c77897cbc2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-31-23-10-00.gh-issue-83895.Jm5tR3.rst @@ -0,0 +1,4 @@ +:mod:`xml.etree.ElementTree` now accepts input larger than 2 GiB +in the C implementation. +The data is fed to Expat in chunks, as :mod:`xml.parsers.expat` already did, +instead of raising :exc:`OverflowError`. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index a49811a338e625..fa178f53e9b3ff 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -3940,6 +3940,27 @@ expat_parse(elementtreestate *st, XMLParserObject *self, const char *data, Py_RETURN_NONE; } +/* Expat takes the length as an int, feed larger data in chunks. */ +#define MAX_CHUNK_SIZE (1 << 20) + +LOCAL(PyObject*) +expat_parse_large(elementtreestate *st, XMLParserObject *self, + const char *data, Py_ssize_t data_len, int final) +{ + static_assert(MAX_CHUNK_SIZE <= INT_MAX, + "MAX_CHUNK_SIZE is larger than INT_MAX"); + while (data_len > MAX_CHUNK_SIZE) { + PyObject *res = expat_parse(st, self, data, MAX_CHUNK_SIZE, 0); + if (res == NULL) { + return NULL; + } + Py_DECREF(res); + data += MAX_CHUNK_SIZE; + data_len -= MAX_CHUNK_SIZE; + } + return expat_parse(st, self, data, (int)data_len, final); +} + /*[clinic input] _elementtree.XMLParser.close @@ -4031,26 +4052,17 @@ _elementtree_XMLParser_feed_impl(XMLParserObject *self, PyObject *data) const char *data_ptr = PyUnicode_AsUTF8AndSize(data, &data_len); if (data_ptr == NULL) return NULL; - if (data_len > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, "size does not fit in an int"); - return NULL; - } /* Explicitly set UTF-8 encoding. Return code ignored. */ (void)EXPAT(st, SetEncoding)(self->parser, "utf-8"); - return expat_parse(st, self, data_ptr, (int)data_len, 0); + return expat_parse_large(st, self, data_ptr, data_len, 0); } else { Py_buffer view; PyObject *res; if (PyObject_GetBuffer(data, &view, PyBUF_SIMPLE) < 0) return NULL; - if (view.len > INT_MAX) { - PyBuffer_Release(&view); - PyErr_SetString(PyExc_OverflowError, "size does not fit in an int"); - return NULL; - } - res = expat_parse(st, self, view.buf, (int)view.len, 0); + res = expat_parse_large(st, self, view.buf, view.len, 0); PyBuffer_Release(&view); return res; } @@ -4120,14 +4132,8 @@ _elementtree_XMLParser__parse_whole_impl(XMLParserObject *self, break; } - if (PyBytes_GET_SIZE(buffer) > INT_MAX) { - Py_DECREF(buffer); - Py_DECREF(reader); - PyErr_SetString(PyExc_OverflowError, "size does not fit in an int"); - return NULL; - } - res = expat_parse( - st, self, PyBytes_AS_STRING(buffer), (int)PyBytes_GET_SIZE(buffer), + res = expat_parse_large( + st, self, PyBytes_AS_STRING(buffer), PyBytes_GET_SIZE(buffer), 0); first = 0; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c01197d15bad5f..f6009e7e73f249 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -39,6 +39,7 @@ static struct PyModuleDef _testcapimodule; // Module state typedef struct { PyObject *error; // _testcapi.error object + Py_ssize_t list_destroys; } testcapistate_t; static testcapistate_t* @@ -2412,6 +2413,36 @@ test_reftracer(PyObject *ob, PyObject *Py_UNUSED(ignored)) return NULL; } +static int +_listdestroytracer(PyObject *obj, PyRefTracerEvent event, void *data) +{ + if (event == PyRefTracer_DESTROY && PyList_CheckExact(obj)) { + _Py_atomic_add_ssize((Py_ssize_t *)data, 1); + } + return 0; +} + +static PyObject * +start_counting_list_destroys(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + testcapistate_t *state = get_testcapi_state(self); + _Py_atomic_store_ssize(&state->list_destroys, 0); + if (PyRefTracer_SetTracer(_listdestroytracer, &state->list_destroys) != 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +stop_counting_list_destroys(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + if (PyRefTracer_SetTracer(NULL, NULL) != 0) { + return NULL; + } + return PyLong_FromSsize_t( + _Py_atomic_load_ssize(&get_testcapi_state(self)->list_destroys)); +} + static PyObject * function_set_warning(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { @@ -3016,6 +3047,8 @@ static PyMethodDef TestMethods[] = { {"test_buildvalue_N", test_buildvalue_N, METH_NOARGS}, {"test_buildvalue_p", test_buildvalue_p, METH_NOARGS}, {"test_reftracer", test_reftracer, METH_NOARGS}, + {"start_counting_list_destroys", start_counting_list_destroys, METH_NOARGS}, + {"stop_counting_list_destroys", stop_counting_list_destroys, METH_NOARGS}, {"_test_thread_state", test_thread_state, METH_VARARGS}, {"gilstate_ensure_release", gilstate_ensure_release, METH_NOARGS}, #ifndef MS_WINDOWS diff --git a/Modules/cjkcodecs/multibytecodec.c b/Modules/cjkcodecs/multibytecodec.c index d90900457574d9..5eb1533bdfc118 100644 --- a/Modules/cjkcodecs/multibytecodec.c +++ b/Modules/cjkcodecs/multibytecodec.c @@ -1482,23 +1482,25 @@ mbstreamreader_iread(MultibyteStreamReaderObject *self, endoffile = (PyBytes_GET_SIZE(cres) == 0); if (self->pendingsize > 0) { - PyObject *ctr; - char *ctrdata; - if (PyBytes_GET_SIZE(cres) > PY_SSIZE_T_MAX - self->pendingsize) { PyErr_NoMemory(); goto errorexit; } rsize = PyBytes_GET_SIZE(cres) + self->pendingsize; - ctr = PyBytes_FromStringAndSize(NULL, rsize); - if (ctr == NULL) + + PyBytesWriter *writer = PyBytesWriter_Create(rsize); + if (writer == NULL) { goto errorexit; - ctrdata = PyBytes_AS_STRING(ctr); + } + char *ctrdata = PyBytesWriter_GetData(writer); memcpy(ctrdata, self->pending, self->pendingsize); memcpy(ctrdata + self->pendingsize, PyBytes_AS_STRING(cres), PyBytes_GET_SIZE(cres)); - Py_SETREF(cres, ctr); + Py_SETREF(cres, PyBytesWriter_Finish(writer)); + if (cres == NULL) { + goto errorexit; + } self->pendingsize = 0; } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 17e6a729dcd83f..e379770e10031d 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1895,6 +1895,31 @@ int PyFloat_Pack2(double x, char *data, int le) { unsigned char *p = (unsigned char *)data; +#if HAVE_FLOAT16 + /* Conversion can change NaNs type or alter payload. Here we + just fallback to the generic code, instead of providing + workarounds as for single/double precision. */ + if (!isnan(x)) { + _Float16 y = (_Float16)x; + + if (isinf(y) && !isinf(x)) { + goto Overflow; + } + + unsigned char s[sizeof(_Float16)]; + + memcpy(s, &y, sizeof(_Float16)); + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { + p[1] = s[0]; + p[0] = s[1]; + } + else { + p[0] = s[0]; + p[1] = s[1]; + } + return 0; + } +#endif unsigned char sign; int e; double f; @@ -2090,6 +2115,24 @@ double PyFloat_Unpack2(const char *data, int le) { unsigned char *p = (unsigned char *)data; +#if HAVE_FLOAT16 + _Float16 x16; + + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { + char buf[2]; + + buf[1] = p[0]; + buf[0] = p[1]; + memcpy(&x16, buf, 2); + } + else { + memcpy(&x16, p, 2); + } + if (!isnan(x16)) { + return x16; + } + /* Fallback to the generic code for NaNs, see PyFloat_Pack2(). */ +#endif unsigned char sign; int e; unsigned int f; diff --git a/Objects/genobject.c b/Objects/genobject.c index 6529a66fc35a6b..6a96bc27d9a950 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -542,8 +542,8 @@ gen_close(PyObject *self, PyObject *args) // Set an exception for a gen.throw() call. // Return 0 on success, -1 on failure. -static int -gen_set_exception(PyObject *typ, PyObject *val, PyObject *tb) +int +_PyGen_SetException(PyObject *typ, PyObject *val, PyObject *tb) { /* First, check the traceback argument, replacing None with NULL. */ @@ -640,7 +640,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit, "cannot reuse already awaited coroutine"); return NULL; } - gen_set_exception(typ, val, tb); + _PyGen_SetException(typ, val, tb); return NULL; } @@ -718,7 +718,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit, throw_here: assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING); - if (gen_set_exception(typ, val, tb) < 0) { + if (_PyGen_SetException(typ, val, tb) < 0) { FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state); return NULL; } diff --git a/Objects/iterobject.c b/Objects/iterobject.c index e323987601d5d4..0394227cd482db 100644 --- a/Objects/iterobject.c +++ b/Objects/iterobject.c @@ -5,7 +5,10 @@ #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _PyEval_GetBuiltin() #include "pycore_genobject.h" // _PyCoro_GetAwaitableIter() +#include "pycore_iterobject.h" // _PyCallIter_NewEx() #include "pycore_object.h" // _PyObject_GC_TRACK() +#include "pycore_pyerrors.h" // _PyErr_FormatFromCause() +#include "pycore_pystate.h" // _PyThreadState_GET() typedef struct { @@ -185,22 +188,44 @@ PyTypeObject PySeqIter_Type = { typedef struct { PyObject_HEAD - PyObject *it_callable; /* Set to NULL when iterator is exhausted */ - PyObject *it_sentinel; /* Set to NULL when iterator is exhausted */ + PyObject *it_callable; /* set to NULL when the iterator is exhausted */ + PyObject *it_sentinel; /* can be NULL, and is when exhausted */ + PyObject *it_stop_exc; /* never NULL */ } calliterobject; PyObject * -PyCallIter_New(PyObject *callable, PyObject *sentinel) +_PyCallIter_NewEx(PyObject *callable, PyObject *sentinel, PyObject *stop_exc) { calliterobject *it; + if (stop_exc == NULL) { + stop_exc = PyExc_StopIteration; + } + else if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) { + return NULL; + } it = PyObject_GC_New(calliterobject, &PyCallIter_Type); if (it == NULL) return NULL; it->it_callable = Py_NewRef(callable); - it->it_sentinel = Py_NewRef(sentinel); + it->it_sentinel = Py_XNewRef(sentinel); + it->it_stop_exc = Py_NewRef(stop_exc); _PyObject_GC_TRACK(it); return (PyObject *)it; } + +PyObject * +PyCallIter_New(PyObject *callable, PyObject *sentinel) +{ + return _PyCallIter_NewEx(callable, sentinel, NULL); +} + +static void +calliter_exhaust(calliterobject *it) +{ + Py_CLEAR(it->it_callable); + Py_CLEAR(it->it_sentinel); +} + static void calliter_dealloc(PyObject *op) { @@ -208,6 +233,7 @@ calliter_dealloc(PyObject *op) _PyObject_GC_UNTRACK(it); Py_XDECREF(it->it_callable); Py_XDECREF(it->it_sentinel); + Py_XDECREF(it->it_stop_exc); PyObject_GC_Del(it); } @@ -217,6 +243,7 @@ calliter_traverse(PyObject *op, visitproc visit, void *arg) calliterobject *it = (calliterobject*)op; Py_VISIT(it->it_callable); Py_VISIT(it->it_sentinel); + Py_VISIT(it->it_stop_exc); return 0; } @@ -231,23 +258,28 @@ calliter_iternext(PyObject *op) } result = _PyObject_CallNoArgs(it->it_callable); - if (result != NULL && it->it_sentinel != NULL){ - int ok; - - ok = PyObject_RichCompareBool(it->it_sentinel, result, Py_EQ); + /* The call can exhaust the iterator re-entrantly. */ + if (result != NULL && it->it_callable != NULL) { + if (it->it_sentinel == NULL) { + return result; /* Common case, fast path */ + } + int ok = PyObject_RichCompareBool(it->it_sentinel, result, Py_EQ); if (ok == 0) { return result; /* Common case, fast path */ } if (ok > 0) { - Py_CLEAR(it->it_callable); - Py_CLEAR(it->it_sentinel); + calliter_exhaust(it); } } - else if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + else if (PyErr_ExceptionMatches(it->it_stop_exc)) { PyErr_Clear(); - Py_CLEAR(it->it_callable); - Py_CLEAR(it->it_sentinel); + calliter_exhaust(it); + } + else if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + /* It would be mistaken for the end of the iteration (see PEP 479). */ + _PyErr_FormatFromCause(PyExc_RuntimeError, + "callable raised StopIteration"); } Py_XDECREF(result); return NULL; @@ -263,14 +295,57 @@ calliter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) * call must be before access of iterator pointers. * see issue #101765 */ - if (it->it_callable != NULL && it->it_sentinel != NULL) - return Py_BuildValue("N(OO)", iter, it->it_callable, it->it_sentinel); - else + if (it->it_callable == NULL) { return Py_BuildValue("N(())", iter); + } + /* Only the sentinel can be passed as an argument of iter(), so other + attributes are restored from the state (see calliter_setstate()). */ + if (it->it_sentinel == NULL) { + return Py_BuildValue("N(OO)(()O)", iter, it->it_callable, Py_None, + it->it_stop_exc); + } + else if (it->it_stop_exc == PyExc_StopIteration) { + return Py_BuildValue("N(OO)", iter, it->it_callable, it->it_sentinel); + } + else { + return Py_BuildValue("N(OO)((O)O)", iter, it->it_callable, Py_None, + it->it_sentinel, it->it_stop_exc); + } +} + +static PyObject * +calliter_setstate(PyObject *op, PyObject *state) +{ + calliterobject *it = (calliterobject*)op; + PyObject *sentinel, *stop_exc; + + if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 2) { + goto error; + } + sentinel = PyTuple_GET_ITEM(state, 0); + stop_exc = PyTuple_GET_ITEM(state, 1); + if (!PyTuple_Check(sentinel) || PyTuple_GET_SIZE(sentinel) > 1) { + goto error; + } + if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) { + return NULL; + } + if (it->it_callable != NULL) { + Py_XSETREF(it->it_sentinel, + PyTuple_GET_SIZE(sentinel) ? + Py_NewRef(PyTuple_GET_ITEM(sentinel, 0)) : NULL); + Py_SETREF(it->it_stop_exc, Py_NewRef(stop_exc)); + } + Py_RETURN_NONE; + +error: + PyErr_SetString(PyExc_TypeError, "invalid state for callable_iterator"); + return NULL; } static PyMethodDef calliter_methods[] = { {"__reduce__", calliter_reduce, METH_NOARGS, reduce_doc}, + {"__setstate__", calliter_setstate, METH_O, setstate_doc}, {NULL, NULL} /* sentinel */ }; @@ -337,10 +412,10 @@ anextawaitable_traverse(PyObject *op, visitproc visit, void *arg) } static PyObject * -anextawaitable_getiter(anextawaitableobject *obj) +awaitable_getiter(PyObject *owner, PyObject *wrapped) { - assert(obj->wrapped != NULL); - PyObject *awaitable = _PyCoro_GetAwaitableIter(obj->wrapped); + assert(wrapped != NULL); + PyObject *awaitable = _PyCoro_GetAwaitableIter(wrapped); if (awaitable == NULL) { return NULL; } @@ -359,7 +434,7 @@ anextawaitable_getiter(anextawaitableobject *obj) if (!PyIter_Check(awaitable)) { PyErr_Format(PyExc_TypeError, "%T.__await__() must return an iterable, not %T", - obj, awaitable); + owner, awaitable); Py_DECREF(awaitable); return NULL; } @@ -391,7 +466,7 @@ anextawaitable_iternext(PyObject *op) * gen.__anext__().__next__() */ anextawaitableobject *obj = anextawaitableobject_CAST(op); - PyObject *awaitable = anextawaitable_getiter(obj); + PyObject *awaitable = awaitable_getiter(op, obj->wrapped); if (awaitable == NULL) { return NULL; } @@ -411,7 +486,7 @@ anextawaitable_iternext(PyObject *op) static PyObject * anextawaitable_proxy(anextawaitableobject *obj, char *meth, PyObject *arg) { - PyObject *awaitable = anextawaitable_getiter(obj); + PyObject *awaitable = awaitable_getiter((PyObject *)obj, obj->wrapped); if (awaitable == NULL) { return NULL; } @@ -540,3 +615,328 @@ PyAnextAwaitable_New(PyObject *awaitable, PyObject *default_value) _PyObject_GC_TRACK(anext); return (PyObject *)anext; } + + +/* -------------------------------------- */ + +/* The asynchronous counterpart of calliterobject: the callable is called + and its result is awaited for every __anext__(). */ + +typedef struct { + PyObject_HEAD + PyObject *it_callable; /* set to NULL when the iterator is exhausted */ + PyObject *it_sentinel; /* can be NULL, and is when exhausted */ + PyObject *it_stop_exc; /* never NULL */ +} acalliterobject; + +#define acalliterobject_CAST(op) ((acalliterobject *)(op)) + +/* The awaitable returned by acalliter_anext(). The callable is only + called when this object is awaited. */ +typedef struct { + PyObject_HEAD + PyObject *aw_iterator; /* the iterator which created this object */ + PyObject *aw_wrapped; /* the awaitable returned by the callable */ + bool aw_closed; +} acallawaitableobject; + +#define acallawaitableobject_CAST(op) ((acallawaitableobject *)(op)) + +PyObject * +_PyACallIter_New(PyObject *callable, PyObject *sentinel, PyObject *stop_exc) +{ + if (stop_exc == NULL) { + stop_exc = PyExc_StopAsyncIteration; + } + else if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) { + return NULL; + } + acalliterobject *it = PyObject_GC_New(acalliterobject, &_PyACallIter_Type); + if (it == NULL) { + return NULL; + } + it->it_callable = Py_NewRef(callable); + it->it_sentinel = Py_XNewRef(sentinel); + it->it_stop_exc = Py_NewRef(stop_exc); + _PyObject_GC_TRACK(it); + return (PyObject *)it; +} + +static void +acalliter_exhaust(acalliterobject *it) +{ + Py_CLEAR(it->it_callable); + Py_CLEAR(it->it_sentinel); +} + +static void +acalliter_dealloc(PyObject *op) +{ + acalliterobject *it = acalliterobject_CAST(op); + _PyObject_GC_UNTRACK(it); + Py_XDECREF(it->it_callable); + Py_XDECREF(it->it_sentinel); + Py_XDECREF(it->it_stop_exc); + PyObject_GC_Del(it); +} + +static int +acalliter_traverse(PyObject *op, visitproc visit, void *arg) +{ + acalliterobject *it = acalliterobject_CAST(op); + Py_VISIT(it->it_callable); + Py_VISIT(it->it_sentinel); + Py_VISIT(it->it_stop_exc); + return 0; +} + +static PyObject *acallawaitable_new(PyObject *iterator); + +static PyObject * +acalliter_anext(PyObject *op) +{ + return acallawaitable_new(op); +} + +static PyAsyncMethods acalliter_as_async = { + 0, /* am_await */ + PyObject_SelfIter, /* am_aiter */ + acalliter_anext, /* am_anext */ + 0, /* am_send */ +}; + +PyTypeObject _PyACallIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "async_callable_iterator", + .tp_basicsize = sizeof(acalliterobject), + .tp_dealloc = acalliter_dealloc, + .tp_as_async = &acalliter_as_async, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = acalliter_traverse, +}; + +/* -------------------------------------- */ + +static PyObject * +acallawaitable_new(PyObject *iterator) +{ + acallawaitableobject *aw = PyObject_GC_New( + acallawaitableobject, &_PyACallIterAwaitable_Type); + if (aw == NULL) { + return NULL; + } + aw->aw_iterator = Py_NewRef(iterator); + aw->aw_wrapped = NULL; + aw->aw_closed = false; + _PyObject_GC_TRACK(aw); + return (PyObject *)aw; +} + +static void +acallawaitable_dealloc(PyObject *op) +{ + acallawaitableobject *aw = acallawaitableobject_CAST(op); + _PyObject_GC_UNTRACK(aw); + Py_XDECREF(aw->aw_iterator); + Py_XDECREF(aw->aw_wrapped); + PyObject_GC_Del(aw); +} + +static int +acallawaitable_traverse(PyObject *op, visitproc visit, void *arg) +{ + acallawaitableobject *aw = acallawaitableobject_CAST(op); + Py_VISIT(aw->aw_iterator); + Py_VISIT(aw->aw_wrapped); + return 0; +} + +/* Call the callable. Return 0 on success, -1 on failure. */ +static int +acallawaitable_start(acallawaitableobject *aw) +{ + acalliterobject *it = acalliterobject_CAST(aw->aw_iterator); + + if (aw->aw_closed) { + PyErr_SetString(PyExc_RuntimeError, + "cannot reuse already awaited __anext__()"); + return -1; + } + if (it->it_callable == NULL) { + PyErr_SetNone(PyExc_StopAsyncIteration); + return -1; + } + PyObject *awaitable = _PyObject_CallNoArgs(it->it_callable); + if (awaitable == NULL) { + if (PyErr_ExceptionMatches(it->it_stop_exc)) { + PyErr_Clear(); + acalliter_exhaust(it); + PyErr_SetNone(PyExc_StopAsyncIteration); + } + else if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + /* It would be mistaken for the result of the await (PEP 525). */ + _PyErr_FormatFromCause(PyExc_RuntimeError, + "callable raised StopIteration"); + } + else if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) { + /* It would be mistaken for the end of the iteration (PEP 525). */ + _PyErr_FormatFromCause(PyExc_RuntimeError, + "callable raised StopAsyncIteration"); + } + return -1; + } + aw->aw_wrapped = awaitable; + return 0; +} + +/* Turn the exception raised by the wrapped awaitable into the result of + the await. Always returns NULL. */ +static PyObject * +acallawaitable_handle_error(acallawaitableobject *aw) +{ + acalliterobject *it = acalliterobject_CAST(aw->aw_iterator); + + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyObject *value; + if (_PyGen_FetchStopIterationValue(&value) < 0) { + return NULL; + } + int ok = 0; + if (it->it_sentinel != NULL) { + ok = PyObject_RichCompareBool(it->it_sentinel, value, Py_EQ); + } + if (ok == 0) { + (void)_PyGen_SetStopIterationValue(value); + } + else if (ok > 0) { + acalliter_exhaust(it); + PyErr_SetNone(PyExc_StopAsyncIteration); + } + Py_DECREF(value); + return NULL; + } + if (PyErr_ExceptionMatches(it->it_stop_exc)) { + PyErr_Clear(); + acalliter_exhaust(it); + PyErr_SetNone(PyExc_StopAsyncIteration); + } + else if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) { + /* It would be mistaken for the end of the iteration (see PEP 525). */ + _PyErr_FormatFromCause(PyExc_RuntimeError, + "callable raised StopAsyncIteration"); + } + return NULL; +} + +static PyObject * +acallawaitable_iternext(PyObject *op) +{ + acallawaitableobject *aw = acallawaitableobject_CAST(op); + + if (aw->aw_wrapped == NULL && acallawaitable_start(aw) < 0) { + return NULL; + } + PyObject *awaitable = awaitable_getiter(op, aw->aw_wrapped); + if (awaitable == NULL) { + return NULL; + } + PyObject *result = (*Py_TYPE(awaitable)->tp_iternext)(awaitable); + Py_DECREF(awaitable); + if (result != NULL) { + return result; + } + return acallawaitable_handle_error(aw); +} + +static PyObject * +acallawaitable_proxy(acallawaitableobject *aw, char *meth, PyObject *arg) +{ + PyObject *awaitable = awaitable_getiter((PyObject *)aw, aw->aw_wrapped); + if (awaitable == NULL) { + return NULL; + } + // When specified, 'arg' may be a tuple (if coming from a METH_VARARGS + // method) or a single object (if coming from a METH_O method). + PyObject *ret = arg == NULL + ? PyObject_CallMethod(awaitable, meth, NULL) + : PyObject_CallMethod(awaitable, meth, "O", arg); + Py_DECREF(awaitable); + if (ret != NULL) { + return ret; + } + return acallawaitable_handle_error(aw); +} + +static PyObject * +acallawaitable_send(PyObject *op, PyObject *arg) +{ + acallawaitableobject *aw = acallawaitableobject_CAST(op); + + if (aw->aw_wrapped == NULL && acallawaitable_start(aw) < 0) { + return NULL; + } + return acallawaitable_proxy(aw, "send", arg); +} + +static PyObject * +acallawaitable_throw(PyObject *op, PyObject *args) +{ + acallawaitableobject *aw = acallawaitableobject_CAST(op); + + if (aw->aw_wrapped == NULL) { + /* Not started, so the exception is raised at the point of the + await, as for a not started coroutine. */ + PyObject *typ, *val = NULL, *tb = NULL; + if (!PyArg_UnpackTuple(args, "throw", 1, 3, &typ, &val, &tb)) { + return NULL; + } + aw->aw_closed = true; + (void)_PyGen_SetException(typ, val, tb); + return NULL; + } + return acallawaitable_proxy(aw, "throw", args); +} + +static PyObject * +acallawaitable_close(PyObject *op, PyObject *Py_UNUSED(dummy)) +{ + acallawaitableobject *aw = acallawaitableobject_CAST(op); + + if (aw->aw_wrapped == NULL) { + /* Not started, so there is nothing to close. */ + aw->aw_closed = true; + Py_RETURN_NONE; + } + PyObject *result = acallawaitable_proxy(aw, "close", NULL); + aw->aw_closed = true; + return result; +} + +static PyMethodDef acallawaitable_methods[] = { + {"send", acallawaitable_send, METH_O, send_doc}, + {"throw", acallawaitable_throw, METH_VARARGS, throw_doc}, + {"close", acallawaitable_close, METH_NOARGS, close_doc}, + {NULL, NULL} /* Sentinel */ +}; + +static PyAsyncMethods acallawaitable_as_async = { + PyObject_SelfIter, /* am_await */ + 0, /* am_aiter */ + 0, /* am_anext */ + 0, /* am_send */ +}; + +PyTypeObject _PyACallIterAwaitable_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "async_callable_iterator_awaitable", + .tp_basicsize = sizeof(acallawaitableobject), + .tp_dealloc = acallawaitable_dealloc, + .tp_as_async = &acallawaitable_as_async, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = acallawaitable_traverse, + .tp_iter = PyObject_SelfIter, + .tp_iternext = acallawaitable_iternext, + .tp_methods = acallawaitable_methods, +}; diff --git a/Objects/object.c b/Objects/object.c index fadd9273a36607..856d9fc41a4154 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2519,6 +2519,8 @@ _PyObject_FiniState(PyInterpreterState *interp) } +extern PyTypeObject _PyACallIter_Type; +extern PyTypeObject _PyACallIterAwaitable_Type; extern PyTypeObject _PyAnextAwaitable_Type; extern PyTypeObject _PyLegacyEventHandler_Type; extern PyTypeObject _PyLineIterator; @@ -2612,6 +2614,8 @@ static PyTypeObject* static_types[_Py_NUM_MANAGED_PREINITIALIZED_TYPES] = { &PyWrapperDescr_Type, &PyZip_Type, &Py_GenericAliasType, + &_PyACallIter_Type, + &_PyACallIterAwaitable_Type, &_PyAnextAwaitable_Type, &_PyAsyncGenASend_Type, &_PyAsyncGenAThrow_Type, @@ -3224,6 +3228,10 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate) * up distorting allocation statistics. */ _PyObject_ASSERT(op, Py_REFCNT(op) == 0); +#ifdef Py_TRACE_REFS + _Py_ForgetReference(op); +#endif + _PyReftracerTrack(op, PyRefTracer_DESTROY); (*dealloc)(op); } } diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 33647ec284061f..79dfc9ccf39ec2 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -276,6 +276,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 434dd13267fe93..765b4d46b12dd0 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -747,6 +747,9 @@ Include\cpython + + Include\cpython + Include\internal diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index cbe59c8883d5a5..d28e6fa9cd01ae 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -10,6 +10,7 @@ #include "pycore_floatobject.h" // _PyFloat_ExactDealloc() #include "pycore_interp.h" // _PyInterpreterState_GetConfig() #include "pycore_import.h" // _PyImport_LazyImportModuleLevelObject () +#include "pycore_iterobject.h" // _PyCallIter_NewEx() #include "pycore_long.h" // _PyLong_CompactValue #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _Py_AddToAllObjects() @@ -1893,50 +1894,70 @@ builtin_hex(PyObject *module, PyObject *integer) } -/* AC: cannot convert yet, as needs PEP 457 group support in inspect */ +/*[clinic input] +@text_signature "($module, object, /, [stop_value], *, stop_exception=StopIteration)" +iter as builtin_iter + + object: object + / + stop_value: object = NULL + * + stop_exception: object = NULL + +Get an iterator from an object. + +In the first form, the argument must supply its own iterator, or be a +sequence. In the second form, the callable is called until it returns +the stop value or raises the specified exception. +[clinic start generated code]*/ + static PyObject * -builtin_iter(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +builtin_iter_impl(PyObject *module, PyObject *object, PyObject *stop_value, + PyObject *stop_exception) +/*[clinic end generated code: output=eb9c9ae8f77bf400 input=d3a2f767f29d9ae6]*/ { - PyObject *v; - - if (!_PyArg_CheckPositional("iter", nargs, 1, 2)) - return NULL; - v = args[0]; - if (nargs == 1) - return PyObject_GetIter(v); - if (!PyCallable_Check(v)) { + if (stop_value == NULL && stop_exception == NULL) { + return PyObject_GetIter(object); + } + if (!PyCallable_Check(object)) { PyErr_SetString(PyExc_TypeError, - "iter(v, w): v must be callable"); + "iter(): the first argument must be callable"); return NULL; } - PyObject *sentinel = args[1]; - return PyCallIter_New(v, sentinel); + return _PyCallIter_NewEx(object, stop_value, stop_exception); } -PyDoc_STRVAR(iter_doc, -"iter(iterable) -> iterator\n\ -iter(callable, sentinel) -> iterator\n\ -\n\ -Get an iterator from an object. In the first form, the argument must\n\ -supply its own iterator, or be a sequence.\n\ -In the second form, the callable is called until it returns the\n\ -sentinel."); - /*[clinic input] +@text_signature "($module, object, /, [stop_value], *, stop_exception=StopAsyncIteration)" aiter as builtin_aiter - async_iterable: object + object: object / + stop_value: object = NULL + * + stop_exception: object = NULL Return an AsyncIterator for an AsyncIterable object. + +In the second form, the callable is called and its result is awaited +until it returns the stop value or raises the specified exception. [clinic start generated code]*/ static PyObject * -builtin_aiter(PyObject *module, PyObject *async_iterable) -/*[clinic end generated code: output=1bae108d86f7960e input=473993d0cacc7d23]*/ +builtin_aiter_impl(PyObject *module, PyObject *object, PyObject *stop_value, + PyObject *stop_exception) +/*[clinic end generated code: output=2865edb3fbc45693 input=2adb37d12adafd0c]*/ { - return PyObject_GetAIter(async_iterable); + if (stop_value == NULL && stop_exception == NULL) { + return PyObject_GetAIter(object); + } + if (!PyCallable_Check(object)) { + PyErr_SetString(PyExc_TypeError, + "aiter(): the first argument must be callable"); + return NULL; + } + return _PyACallIter_New(object, stop_value, stop_exception); } PyObject *PyAnextAwaitable_New(PyObject *, PyObject *); @@ -3472,7 +3493,7 @@ static PyMethodDef builtin_methods[] = { BUILTIN_INPUT_METHODDEF BUILTIN_ISINSTANCE_METHODDEF BUILTIN_ISSUBCLASS_METHODDEF - {"iter", _PyCFunction_CAST(builtin_iter), METH_FASTCALL, iter_doc}, + BUILTIN_ITER_METHODDEF BUILTIN_AITER_METHODDEF BUILTIN_LEN_METHODDEF BUILTIN_LOCALS_METHODDEF diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index 4a38e0df61708c..c10bb03d817816 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -850,14 +850,166 @@ PyDoc_STRVAR(builtin_hex__doc__, #define BUILTIN_HEX_METHODDEF \ {"hex", (PyCFunction)builtin_hex, METH_O, builtin_hex__doc__}, +PyDoc_STRVAR(builtin_iter__doc__, +"iter($module, object, /, [stop_value], *, stop_exception=StopIteration)\n" +"--\n" +"\n" +"Get an iterator from an object.\n" +"\n" +"In the first form, the argument must supply its own iterator, or be a\n" +"sequence. In the second form, the callable is called until it returns\n" +"the stop value or raises the specified exception."); + +#define BUILTIN_ITER_METHODDEF \ + {"iter", _PyCFunction_CAST(builtin_iter), METH_FASTCALL|METH_KEYWORDS, builtin_iter__doc__}, + +static PyObject * +builtin_iter_impl(PyObject *module, PyObject *object, PyObject *stop_value, + PyObject *stop_exception); + +static PyObject * +builtin_iter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(stop_value), &_Py_ID(stop_exception), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "stop_value", "stop_exception", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "iter", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *object; + PyObject *stop_value = NULL; + PyObject *stop_exception = NULL; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + object = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (args[1]) { + stop_value = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + stop_exception = args[2]; +skip_optional_kwonly: + return_value = builtin_iter_impl(module, object, stop_value, stop_exception); + +exit: + return return_value; +} + PyDoc_STRVAR(builtin_aiter__doc__, -"aiter($module, async_iterable, /)\n" +"aiter($module, object, /, [stop_value], *, stop_exception=StopAsyncIteration)\n" "--\n" "\n" -"Return an AsyncIterator for an AsyncIterable object."); +"Return an AsyncIterator for an AsyncIterable object.\n" +"\n" +"In the second form, the callable is called and its result is awaited\n" +"until it returns the stop value or raises the specified exception."); #define BUILTIN_AITER_METHODDEF \ - {"aiter", (PyCFunction)builtin_aiter, METH_O, builtin_aiter__doc__}, + {"aiter", _PyCFunction_CAST(builtin_aiter), METH_FASTCALL|METH_KEYWORDS, builtin_aiter__doc__}, + +static PyObject * +builtin_aiter_impl(PyObject *module, PyObject *object, PyObject *stop_value, + PyObject *stop_exception); + +static PyObject * +builtin_aiter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(stop_value), &_Py_ID(stop_exception), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "stop_value", "stop_exception", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "aiter", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *object; + PyObject *stop_value = NULL; + PyObject *stop_exception = NULL; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + object = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (args[1]) { + stop_value = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + stop_exception = args[2]; +skip_optional_kwonly: + return_value = builtin_aiter_impl(module, object, stop_value, stop_exception); + +exit: + return return_value; +} PyDoc_STRVAR(builtin_anext__doc__, "anext($module, async_iterator, default=, /)\n" @@ -1387,4 +1539,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=84efa9c5cc737ce5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5fb1ac6a4253ee2f input=a9049054013a1b77]*/ diff --git a/Python/codecs.c b/Python/codecs.c index 6d1ae651fa0005..84a0589c8e8317 100644 --- a/Python/codecs.c +++ b/Python/codecs.c @@ -1426,19 +1426,19 @@ _PyCodec_SurrogateEscapeUnicodeEncodeError(PyObject *exc) return NULL; } - PyObject *res = PyBytes_FromStringAndSize(NULL, slen); - if (res == NULL) { + PyBytesWriter *writer = PyBytesWriter_Create(slen); + if (writer == NULL) { Py_DECREF(obj); return NULL; } - char *outp = PyBytes_AsString(res); + char *outp = PyBytesWriter_GetData(writer); for (Py_ssize_t i = start; i < end; i++) { Py_UCS4 ch = PyUnicode_READ_CHAR(obj, i); if (ch < 0xdc80 || ch > 0xdcff) { /* Not a UTF-8b surrogate, fail with original exception. */ Py_DECREF(obj); - Py_DECREF(res); + PyBytesWriter_Discard(writer); PyErr_SetObject(PyExceptionInstance_Class(exc), exc); return NULL; } @@ -1446,6 +1446,8 @@ _PyCodec_SurrogateEscapeUnicodeEncodeError(PyObject *exc) } Py_DECREF(obj); + PyObject *res = PyBytesWriter_Finish(writer); + // Py_BuildValue() propagates the exception if res is NULL return Py_BuildValue("(Nn)", res, end); } diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index db575d870be5c5..148f6e68ab806e 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -58,6 +58,8 @@ Objects/genobject.c - _PyCoroWrapper_Type - Objects/interpolationobject.c - _PyInterpolation_Type - Objects/iterobject.c - PyCallIter_Type - Objects/iterobject.c - PySeqIter_Type - +Objects/iterobject.c - _PyACallIter_Type - +Objects/iterobject.c - _PyACallIterAwaitable_Type - Objects/iterobject.c - _PyAnextAwaitable_Type - Objects/lazyimportobject.c - PyLazyImport_Type - Objects/listobject.c - PyListIter_Type - @@ -73,6 +75,8 @@ Objects/moduleobject.c - PyModule_Type - Objects/namespaceobject.c - _PyNamespace_Type - Objects/object.c - _PyNone_Type - Objects/object.c - _PyNotImplemented_Type - +Objects/object.c - _PyACallIter_Type - +Objects/object.c - _PyACallIterAwaitable_Type - Objects/object.c - _PyAnextAwaitable_Type - Objects/odictobject.c - PyODictItems_Type - Objects/odictobject.c - PyODictIter_Type - diff --git a/configure b/configure index 6b560fe6841b72..e0ad04b036aeb1 100755 --- a/configure +++ b/configure @@ -16724,6 +16724,65 @@ printf "%s\n" "#define _Py_FFI_SUPPORT_C_COMPLEX 1" >>confdefs.h fi +# Check for native half-float type (_Float16). +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _Float16 support" >&5 +printf %s "checking for _Float16 support... " >&6; } +if test ${ac_cv_float16_supported+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) save_CFLAGS=$CFLAGS +save_CPPFLAGS=$CPPFLAGS +save_LDFLAGS=$LDFLAGS +save_LIBS=$LIBS + + +CFLAGS="$CFLAGS -O0" +if test "$cross_compiling" = yes +then : + ac_cv_float16_supported=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int main(void) +{ + _Float16 val = 1.0f16; + double d = 3.14; + val = d; + return 0; +} + +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + ac_cv_float16_supported=yes +else case e in #( + e) ac_cv_float16_supported=no ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + +CFLAGS=$save_CFLAGS +CPPFLAGS=$save_CPPFLAGS +LDFLAGS=$save_LDFLAGS +LIBS=$save_LIBS + + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_float16_supported" >&5 +printf "%s\n" "$ac_cv_float16_supported" >&6; } +if test "x$ac_cv_float16_supported" = xyes +then : + +printf "%s\n" "#define HAVE_FLOAT16 1" >>confdefs.h + +fi + pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libmpdec >= 2.5.0" >&5 diff --git a/configure.ac b/configure.ac index 476f13c82bbb2b..2b8a5b052878c9 100644 --- a/configure.ac +++ b/configure.ac @@ -4469,6 +4469,25 @@ if test "$ac_cv_ffi_complex_double_supported" = "yes"; then [Defined if _Complex C type can be used with libffi.]) fi +# Check for native half-float type (_Float16). +AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], +WITH_SAVE_ENV([ +CFLAGS="$CFLAGS -O0" +AC_RUN_IFELSE([AC_LANG_SOURCE([[ +int main(void) +{ + _Float16 val = 1.0f16; + double d = 3.14; + val = d; + return 0; +} +]])], [ac_cv_float16_supported=yes], +[ac_cv_float16_supported=no], +[ac_cv_float16_supported=no])])) +AS_VAR_IF([ac_cv_float16_supported], [yes], + [AC_DEFINE([HAVE_FLOAT16], [1], + [Defined if _Float16 C type is supported])]) + dnl Check for libmpdec >= 2.5.0 PKG_CHECK_MODULES([LIBMPDEC], [libmpdec >= 2.5.0], [have_mpdec=yes], [ WITH_SAVE_ENV([ diff --git a/pyconfig.h.in b/pyconfig.h.in index 64b5c52790b458..c2a36afe334763 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -497,6 +497,9 @@ /* Define if you have the 'ffi_prep_closure_loc' function. */ #undef HAVE_FFI_PREP_CLOSURE_LOC +/* Defined if _Float16 C type is supported */ +#undef HAVE_FLOAT16 + /* Define to 1 if you have the 'flock' function. */ #undef HAVE_FLOCK