From f8bbe696f152d302e7eb3eeb0587a1933cae59fc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 21 Sep 2026 03:53:04 +0300 Subject: [PATCH 1/7] gh-64007: Do not colorize response to input() in the IDLE Shell (#157700) The colorizer treated all text after the I/O mark as code. Now it skips it while the Shell reads a line for input(), and the colors of text typed ahead are removed. --------- Co-authored-by: Terry Jan Reedy --- Lib/idlelib/idle_test/test_pyshell.py | 36 +++++++++++++++++++ Lib/idlelib/pyshell.py | 12 +++++-- ...-17-21-00-00.gh-issue-64007.inputcolor.rst | 2 ++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-17-21-00-00.gh-issue-64007.inputcolor.rst diff --git a/Lib/idlelib/idle_test/test_pyshell.py b/Lib/idlelib/idle_test/test_pyshell.py index 0e292e7be87ee48..4aa0ba3a90158a8 100644 --- a/Lib/idlelib/idle_test/test_pyshell.py +++ b/Lib/idlelib/idle_test/test_pyshell.py @@ -109,6 +109,42 @@ def test_output_at_prompt(self): self.assertEqual(shell.shell_sidebar.line_prompts, {3: '>>>'}) +class InputStatementlTest(unittest.TestCase): + # Test handling of response to input statements in user code. + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.shell = pyshell.PyShell(pyshell.PyShellFileList(cls.root)) + # As after begin(). + cls.shell.text.mark_set('iomark', 'insert') + cls.shell.text.mark_gravity('iomark', 'left') + + @classmethod + def tearDownClass(cls): + cls.shell.close() + del cls.shell + cls.root.destroy() + del cls.root + + def test_input_not_colorized(self): + # gh-64007: input for input() is not colorized, unlike code. + shell = self.shell + text = shell.text + color = shell.color + shell.resetoutput() + color.reading = True + text.insert('end-1c', 'for x in y') + color.recolorize_main() + self.assertEqual(text.tag_ranges('KEYWORD'), ()) + color.reading = False + color.notify_range('iomark', 'end') + color.recolorize_main() + self.assertEqual(len(text.tag_ranges('KEYWORD')), 4) + + class PyShellRemoveLastNewlineAndSurroundingWhitespaceTest(unittest.TestCase): regexp = pyshell.PyShell._last_newline_re diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index a7374377b1a34b6..953394a870c8d34 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -335,9 +335,13 @@ def open_shell(self, event=None): class ModifiedColorDelegator(ColorDelegator): "Extend base class: colorizer for the shell window itself" + reading = False # True while the user enters input for input(). + def recolorize_main(self): - self.tag_remove("TODO", "1.0", "iomark") - self.tag_add("SYNC", "1.0", "iomark") + # Do not colorize output, nor input for input() (gh-64007). + end = "end" if self.reading else "iomark" + self.tag_remove("TODO", "1.0", end) + self.tag_add("SYNC", "1.0", end) ColorDelegator.recolorize_main(self) def removecolors(self): @@ -1189,9 +1193,13 @@ def readline(self): save = self.reading try: self.reading = True + # Input is not Python code (gh-64007). + self.color.reading = True + self.color.removecolors() self.top.mainloop() # nested mainloop() finally: self.reading = save + self.color.reading = save if self._stop_readline_flag: self._stop_readline_flag = False return "" diff --git a/Misc/NEWS.d/next/IDLE/2026-09-17-21-00-00.gh-issue-64007.inputcolor.rst b/Misc/NEWS.d/next/IDLE/2026-09-17-21-00-00.gh-issue-64007.inputcolor.rst new file mode 100644 index 000000000000000..2e77f55751fa318 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-17-21-00-00.gh-issue-64007.inputcolor.rst @@ -0,0 +1,2 @@ +IDLE no longer applies syntax coloring to the text entered for +:func:`input` in the Shell. From 5afcd82089b6b1a6cf62b41a428cbe818b867364 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 20 Sep 2026 23:36:33 -0500 Subject: [PATCH 2/7] gh-154942: Document the kde() data cache logic. (gh-157725) --- Doc/library/statistics.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst index dba0e26787d9516..9ff30211ebcf157 100644 --- a/Doc/library/statistics.rst +++ b/Doc/library/statistics.rst @@ -306,6 +306,11 @@ However, for reading convenience, most of the examples show sorted sequences. .. image:: kde_example.png :alt: Scatter plot of the estimated probability density function. + Because the returned ``f_hat`` function is typically called many times, + it caches the *data* for performance. To support dynamic datasets, this + cache automatically refreshes whenever the length of the *data* changes. + This allows new samples to be added as they become available. + .. versionadded:: 3.13 From f8b0e26a6ac72beb5db8cd8c22ebfd6e93349a6a Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Sun, 20 Sep 2026 23:53:10 -0500 Subject: [PATCH 3/7] gh-150737: Optimize bytecode for empty unpack cases such as ``{*()}``. (#150812) --- Lib/test/test_compile.py | 154 ++++++++++++++++++ ...-06-02-16-21-02.gh-issue-150737.LoYUFY.rst | 1 + Python/codegen.c | 42 ++++- 3 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 959732fc6e4a83d..553ac70d83a802c 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1145,6 +1145,160 @@ def or_false(x): self.assertIn('LOAD_', opcodes[-2].opname) self.assertEqual('RETURN_VALUE', opcodes[-1].opname) + def test_empty_set_unpack_literal_bytecode_optimization(self): + cases = { + # optimized cases + '{*()}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('RETURN_VALUE', None), + ], + '{*(), *()}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('RETURN_VALUE', None), + ], + '{*(), 1}': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_SET', 1), + ('RETURN_VALUE', None), + ], + '{*(), 1, 2, 3}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', frozenset({1, 2, 3})), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + '{1, *()}': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_SET', 1), + ('RETURN_VALUE', None), + ], + '{1, 2, 3, *()}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', frozenset({1, 2, 3})), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + '{1, 2, *(), 3}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', frozenset({1, 2, 3})), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + # unoptimized cases + '{*(1,)}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', (1,)), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + '{*(x,)}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_NAME', 'x'), + ('BUILD_TUPLE', 1), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + } + + for source, expected in cases.items(): + with self.subTest(source=source): + code = compile(source, '', 'eval') + instructions = [ + (instruction.opname, instruction.argval) + for instruction in dis.get_instructions(code) + ] + self.assertEqual(instructions, expected) + + def test_empty_leading_tuple_unpack_list_and_tuple_bytecode_optimization(self): + cases = { + # optimized cases + '[*()]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('RETURN_VALUE', None), + ], + '[*(), *()]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('RETURN_VALUE', None), + ], + '[*(), 1]': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_LIST', 1), + ('RETURN_VALUE', None), + ], + '(*(),)': [ + ('RESUME', 0), + ('LOAD_COMMON_CONSTANT', ()), + ('RETURN_VALUE', None), + ], + '(*(), *())': [ + ('RESUME', 0), + ('LOAD_COMMON_CONSTANT', ()), + ('RETURN_VALUE', None), + ], + '(*(), 1)': [ + ('RESUME', 0), + ('LOAD_CONST', (1,)), + ('RETURN_VALUE', None), + ], + '[1, *()]': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_LIST', 1), + ('RETURN_VALUE', None), + ], + '[1, 2, 3, *()]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_CONST', (1, 2, 3)), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + '[1, 2, *(), 3]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_CONST', (1, 2, 3)), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + # unoptimized cases + '[*(1,)]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_CONST', (1,)), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + '[*(x,)]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_NAME', 'x'), + ('BUILD_TUPLE', 1), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + } + + for source, expected in cases.items(): + with self.subTest(source=source): + code = compile(source, '', 'eval') + instructions = [ + (instruction.opname, instruction.argval) + for instruction in dis.get_instructions(code) + ] + self.assertEqual(instructions, expected) + def test_imported_load_method(self): sources = [ """\ diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst new file mode 100644 index 000000000000000..35c2e562cf60835 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst @@ -0,0 +1 @@ +Optimize bytecode for empty unpack cases such as ``{*()}``. diff --git a/Python/codegen.c b/Python/codegen.c index c12baf6b15a6dec..c4b749a539efdd4 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3454,24 +3454,46 @@ codegen_boolop(compiler *c, expr_ty e) return SUCCESS; } +static bool +is_empty_starred_literal(expr_ty elt) +{ + if (elt->kind != Starred_kind) { + return false; + } + expr_ty value = elt->v.Starred.value; + return (value->kind == Tuple_kind && + asdl_seq_LEN(value->v.Tuple.elts) == 0) || + (value->kind == List_kind && + asdl_seq_LEN(value->v.List.elts) == 0) || + (value->kind == Dict_kind && + asdl_seq_LEN(value->v.Dict.keys) == 0); +} + static int starunpack_helper_impl(compiler *c, location loc, asdl_expr_seq *elts, PyObject *injected_arg, int pushed, int build, int add, int extend, int tuple) { - Py_ssize_t n = asdl_seq_LEN(elts); - int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE; + Py_ssize_t end = asdl_seq_LEN(elts); + Py_ssize_t n = 0; int seen_star = 0; - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 0; i < end; i++) { expr_ty elt = asdl_seq_GET(elts, i); if (elt->kind == Starred_kind) { + if (is_empty_starred_literal(elt)) { + continue; + } seen_star = 1; - break; } + n++; } + int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE; if (!seen_star && !big) { - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 0; i < end; i++) { expr_ty elt = asdl_seq_GET(elts, i); + if (is_empty_starred_literal(elt)) { + continue; + } VISIT(c, expr, elt); } if (injected_arg) { @@ -3486,15 +3508,20 @@ starunpack_helper_impl(compiler *c, location loc, return SUCCESS; } int sequence_built = 0; + Py_ssize_t nitems = 0; if (big) { ADDOP_I(c, loc, build, pushed); sequence_built = 1; } - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 0; i < end; i++) { expr_ty elt = asdl_seq_GET(elts, i); + if (elt->kind == Starred_kind) { + if (is_empty_starred_literal(elt)) { + continue; + } if (sequence_built == 0) { - ADDOP_I(c, loc, build, i+pushed); + ADDOP_I(c, loc, build, nitems+pushed); sequence_built = 1; } VISIT(c, expr, elt->v.Starred.value); @@ -3506,6 +3533,7 @@ starunpack_helper_impl(compiler *c, location loc, ADDOP_I(c, loc, add, 1); } } + nitems++; } assert(sequence_built); if (injected_arg) { From e9ae46f02b073d52f277152f5e22128525a9e3f7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:52:32 +0300 Subject: [PATCH 4/7] gh-133879: Copyedit "What's New in Python 3.15": add intro (#157867) Co-authored-by: Stan Ulbrych --- Doc/whatsnew/3.15.rst | 168 ++++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 48 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 4095058961b7351..aabc936a24af79c 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -62,41 +62,91 @@ Summary -- Release highlights .. This section singles out the most important changes in Python 3.15. Brevity is key. +Python 3.15 will be the latest stable release of the Python programming +language, with a mix of changes to the language, the implementation, +and the standard library. +The biggest changes include +:ref:`lazy imports `, +:ref:`frozendict ` and +:ref:`sentinel ` builtins, +:ref:`UTF-8 as the default encoding `, +:ref:`unpacking in comprehensions `, +and a :ref:`stable ABI for free-threaded builds `. + +The library changes include a new +:ref:`profiling package ` with +:ref:`Tachyon `, +a high-frequency statistical sampling profiler, +:ref:`more color ` in command-line output, +as well as the usual deprecations and removals, +and improvements in user-friendliness and correctness. + +This article doesn't attempt to provide a complete specification +of all new features, but instead gives a convenient overview. +For full details refer to the documentation, +such as the :ref:`Library Reference ` +and :ref:`Language Reference `. +To understand the complete implementation and design rationale for a change, +refer to the PEP for a particular new feature; +but note that PEPs usually are not kept up-to-date +once a feature has been fully implemented. + +See `Porting to Python 3.15`_ for guidance on upgrading from +earlier versions of Python. + +-------------- .. PEP-sized items next. +Interpreter improvements: + +* :pep:`661`: :ref:`Add sentinel built-in type + ` +* :pep:`686`: :ref:`Python now uses UTF-8 as the default encoding + ` +* :pep:`798`: :ref:`Unpacking in comprehensions + ` * :pep:`810`: :ref:`Explicit lazy imports for faster startup times ` * :pep:`814`: :ref:`Add frozendict built-in type ` -* :pep:`661`: :ref:`Add sentinel built-in type - ` +* :pep:`829`: :ref:`Package startup configuration files ` +* :ref:`The experimental JIT compiler has been significantly upgraded ` +* :ref:`Improved error messages ` + +Significant improvements in the standard library: + * :pep:`799`: :ref:`A dedicated profiling package for organizing Python profiling tools ` -* :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler +* :pep:`799`: :ref:`Tachyon: High-frequency statistical sampling profiler ` -* :pep:`831`: :ref:`Frame pointers are enabled by default for improved - system-level observability ` -* :pep:`798`: :ref:`Unpacking in comprehensions - ` -* :pep:`686`: :ref:`Python now uses UTF-8 as the default encoding - ` -* :pep:`829`: :ref:`Package startup configuration files ` +* :ref:`More color ` + +New typing features: + * :pep:`728`: :ref:`TypedDict with typed extra items ` * :pep:`747`: :ref:`Annotating type forms with TypeForm ` * :pep:`800`: :ref:`Disjoint bases in the type system ` + +C API improvements: + * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object ` +* :pep:`788`: :ref:`Protection against finalization in the C API ` * :pep:`803`, :pep:`820 <820>`, :pep:`793 <793>`: :ref:`Stable ABI for free-threaded builds ` and related C API -* :pep:`788`: :ref:`Protection against finalization in the C API ` -* :ref:`The JIT compiler has been significantly upgraded ` + +Build changes: + +* :pep:`831`: :ref:`Frame pointers are enabled by default for improved + system-level observability ` + +Release changes: + * :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter ` -* :ref:`Improved error messages ` -* :ref:`More color ` New features @@ -205,10 +255,10 @@ as lazy, with the same semantics as the ``lazy`` keyword:: import json # lazy import os # still eager -.. seealso:: :pep:`810` for the full specification and rationale. - (Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.) +.. seealso:: :pep:`810` for the full specification and rationale. + .. _whatsnew315-frozendict: @@ -251,10 +301,10 @@ updated to ``isinstance(arg, (dict, frozendict))`` to accept also the :class:`!frozendict` type, or to ``isinstance(arg, collections.abc.Mapping)`` to accept also other mapping types such as :class:`~types.MappingProxyType`. -.. seealso:: :pep:`814` for the full specification and rationale. - (Contributed by Victor Stinner and Donghee Na in :gh:`141510`.) +.. seealso:: :pep:`814` for the full specification and rationale. + .. _whatsnew315-sentinel: @@ -267,7 +317,7 @@ objects preserve identity when copied, support use in type expressions with the ``|`` operator, and can be pickled when they are importable by module and name. -(PEP by Tal Einat; contributed by Jelle Zijlstra in :gh:`148829`.) +(Contributed by Jelle Zijlstra in :gh:`148829`; PEP 661 written by Tal Einat.) .. seealso:: :pep:`661` for further details. @@ -287,19 +337,20 @@ profiling tools under a single, coherent namespace. This module contains: The ``cProfile`` module remains as an alias for backwards compatibility. The :mod:`profile` module is deprecated and will be removed in Python 3.17. -.. seealso:: :pep:`799` for further details. - (Contributed by Pablo Galindo and László Kiss Kollár in :gh:`138122`.) +.. seealso:: :pep:`799` for further details. + .. _whatsnew315-sampling-profiler: -Tachyon: High frequency statistical sampling profiler +Tachyon: High-frequency statistical sampling profiler ----------------------------------------------------- .. image:: ../../Lib/profiling/sampling/_assets/tachyon-logo.png :alt: Tachyon profiler logo :align: center + :class: no-scaled-link :width: 200px A new statistical sampling profiler (Tachyon) has been added as @@ -452,9 +503,10 @@ This change also extends to asynchronous generator expressions, such that, for example, ``(*a async for a in agen())`` is equivalent to ``(x async for a in agen() for x in a)``. +(Contributed by Adam Hartz in :gh:`143055`.) + .. seealso:: :pep:`798` for further details. -(Contributed by Adam Hartz in :gh:`143055`.) .. _whatsnew315-startup-files: @@ -562,10 +614,10 @@ In addition, APIs in the ``PyGILState`` family (most notably code will continue to work, but there will be no new ``PyGILState`` APIs in future versions of Python. -.. seealso:: :pep:`788` for further details. - (Contributed by Peter Bierma in :gh:`149101`.) +.. seealso:: :pep:`788` for further details. + .. _whatsnew315-improved-error-messages: @@ -581,6 +633,9 @@ Improved error messages .. code-block:: python + from dataclasses import dataclass + from math import pi + @dataclass class Circle: radius: float @@ -602,7 +657,7 @@ Improved error messages .. code-block:: pytb Traceback (most recent call last): - File "/home/pablogsal/github/python/main/lel.py", line 42, in + File "example.py", line 18, in print(container.area) ^^^^^^^^^^^^^^ AttributeError: 'Container' object has no attribute 'area'. Did you mean '.inner.area' instead of '.area'? @@ -694,13 +749,13 @@ Other language changes the :envvar:`PYTHONUTF8=0 ` environment variable or the :option:`-X utf8=0 <-X>` command-line option. + (Contributed by Adam Turner in :gh:`133711`; PEP 686 written by Inada Naoki.) + .. seealso:: :pep:`686` for further details. .. _UTF-8: https://en.wikipedia.org/wiki/UTF-8 .. _Unicode: https://home.unicode.org/ - (Contributed by Adam Turner in :gh:`133711`; PEP 686 written by Inada Naoki.) - .. _whatsnew315-color-interpreter-help: * The interpreter help (such as ``python --help``) is now in color. @@ -884,7 +939,7 @@ Other language changes 3.13, is no longer set or taken into consideration by the import system or standard library. Use :attr:`__spec__.cached ` instead. - (Contributed by Brett Cannon in :gh:`97879`) + (Contributed by Brett Cannon in :gh:`97879`.) Note that the :attr:`~module.__loader__` and :attr:`~module.__package__` attributes are also deprecated and scheduled for removal. @@ -1133,6 +1188,7 @@ dataclasses * Annotations for generated ``__init__`` methods no longer include internal type names. + (Contributed by David Ellis in :gh:`137530`.) dbm @@ -1199,6 +1255,7 @@ gc of significant memory pressure in production environments, it has been reverted back to the generational GC from 3.13. This is the GC now used in Python 3.14.5 and later and Python 3.15. + (Contributed by Sergey Miryanov in :gh:`148726`.) hashlib @@ -1245,7 +1302,7 @@ http.server HTTP responses. (Contributed by Anton I. Sipos in :gh:`135057`.) -* Add a ``-H/--header`` option to the :program:`python -m http.server` +* Add a :option:`-H/--header ` option to the :program:`python -m http.server` command-line interface to support custom headers in HTTP responses. (Contributed by Anton I. Sipos in :gh:`135057`.) @@ -1278,7 +1335,7 @@ json :func:`~json.loads` functions: allow a callback for JSON literal array types to customize Python lists in the resulting decoded object. Passing combined :class:`frozendict` to - *object_pairs_hook* param and :class:`tuple` to ``array_hook`` will yield a + *object_pairs_hook* parameter and :class:`tuple` to *array_hook* will yield a deeply nested immutable Python structure representing the JSON data. (Contributed by Joao S. O. Bueno in :gh:`146440`.) @@ -1313,8 +1370,10 @@ mimetypes John Franey in :gh:`144217`, :gh:`145720`, :gh:`140937`, :gh:`139959`, :gh:`145698`, :gh:`145718`, :gh:`145918`, and :gh:`144213`.) + * Rename ``application/x-texinfo`` to ``application/texinfo``. (Contributed by Charlie Lin in :gh:`140165`.) + * Changed the MIME type for ``.ai`` files to ``application/pdf``. (Contributed by Stan Ulbrych in :gh:`141239`.) @@ -1434,6 +1493,7 @@ shelve * Added new :meth:`!reorganize` method to :mod:`shelve` used to recover unused free space previously occupied by deleted entries. (Contributed by Andrea Oliveri in :gh:`134004`.) + * Add support for custom serialization and deserialization functions in the :mod:`shelve` module. (Contributed by Furkan Onder in :gh:`99631`.) @@ -1459,7 +1519,7 @@ sqlite3 * The :ref:`command-line interface ` has several new features: - * SQL keyword completion on . + * SQL keyword completion on :kbd:`Tab`. (Contributed by Long Tan in :gh:`133393`.) .. _whatsnew315-color-sqlite3: @@ -1469,7 +1529,7 @@ sqlite3 details. (Contributed by Stan Ulbrych and Łukasz Langa in :gh:`133461`.) - * Table, index, trigger, view, column, function, and schema completion on . + * Table, index, trigger, view, column, function, and schema completion on :kbd:`Tab`. (Contributed by Long Tan in :gh:`136101`.) @@ -1572,27 +1632,32 @@ tarfile * :func:`~tarfile.data_filter` now normalizes symbolic link targets in order to avoid path traversal attacks. (Contributed by Petr Viktorin in :gh:`127987` and :cve:`2025-4138`.) + * :func:`~tarfile.TarFile.extractall` now skips fixing up directory attributes when a directory was removed or replaced by another kind of file. (Contributed by Petr Viktorin in :gh:`127987` and :cve:`2024-12718`.) + * :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall` now (re-)apply the extraction filter when substituting a link (hard or symbolic) with a copy of another archive member, and when fixing up directory attributes. The former raises a new exception, :exc:`~tarfile.LinkFallbackError`. (Contributed by Petr Viktorin for :cve:`2025-4330` and :cve:`2024-12718`.) + * :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall` no longer extract rejected members when :func:`~tarfile.TarFile.errorlevel` is zero. (Contributed by Matt Prodani and Petr Viktorin in :gh:`112887` and :cve:`2025-4435`.) + * :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall` now replace slashes with backslashes in symlink targets on Windows to prevent creation of corrupted links. (Contributed by Christoph Walcher in :gh:`57911`.) + * :func:`~tarfile.TarFile.gettarinfo` now replaces backslashes with slashes in - symlink targets on Windows to conform to the tar format standard. (Contributed - by Daniele Nicolodi in :gh:`151669`.) + symlink targets on Windows to conform to the tar format standard. + (Contributed by Daniele Nicolodi in :gh:`151669`.) threading @@ -1715,6 +1780,7 @@ types as :class:`types.FrameLocalsProxyType`. This represents the type of the :attr:`frame.f_locals` attribute, as described in :pep:`667`. + (Contributed by Peter Bierma in :gh:`136492`.) typing @@ -1778,6 +1844,7 @@ unicodedata ----------- * The Unicode database has been updated to Unicode 17.0.0. + (Contributed by Benjamin Peterson in :gh:`138706`.) * Add :func:`unicodedata.isxidstart` and :func:`unicodedata.isxidcontinue` functions to check whether a character can start or continue a @@ -1959,8 +2026,8 @@ csv .. _whatsnew315-jit: -Upgraded JIT compiler ---------------------- +Upgraded experimental JIT compiler +---------------------------------- Results from the `pyperformance `__ benchmark suite report @@ -2084,6 +2151,7 @@ collections.abc * :class:`collections.abc.ByteString` has been removed from ``collections.abc.__all__``. :class:`!collections.abc.ByteString` has been deprecated since Python 3.12, and is scheduled for removal in Python 3.17. + (Contributed by Alex Waygood in :gh:`118803`.) ctypes @@ -2193,6 +2261,7 @@ typing * :class:`typing.ByteString` has been removed from ``typing.__all__``. :class:`!typing.ByteString` has been deprecated since Python 3.9, and is scheduled for removal in Python 3.17. + (Contributed by Alex Waygood in :gh:`118803`.) * The undocumented keyword argument syntax for creating :class:`~typing.NamedTuple` classes (for example, @@ -2233,7 +2302,7 @@ Deprecated New deprecations ---------------- -* :mod:`ast` +* :mod:`ast`: * Creating instances of abstract AST nodes (such as :class:`ast.AST` or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20. @@ -2259,7 +2328,7 @@ New deprecations (Contributed by Nikita Sobolev in :gh:`136355`.) -* :mod:`collections.abc` +* :mod:`collections.abc`: * The following statements now cause ``DeprecationWarning``\ s to be emitted at runtime: @@ -2272,6 +2341,7 @@ New deprecations argument to :func:`isinstance` or :func:`issubclass`, but warnings were not previously emitted if it was merely imported or accessed from the :mod:`!collections.abc` module. + (Contributed by Alex Waygood in :gh:`118803`.) * :mod:`hashlib`: @@ -2304,6 +2374,7 @@ New deprecations * Altering :attr:`IMAP4.file ` is now deprecated and slated for removal in Python 3.19. This property is now unused and changing its value does *not* explicitly close the current file. + (Contributed by Bénédikt Tran in :gh:`142307`.) * :mod:`profile`: @@ -2360,6 +2431,7 @@ New deprecations was subclassed or used as the second argument to :func:`isinstance` or :func:`issubclass`, but warnings were not previously emitted if it was merely imported or accessed from the :mod:`!typing` module. + (Contributed by Alex Waygood in :gh:`118803`.) * It is deprecated to call :func:`isinstance` and :func:`issubclass` checks on protocol classes that were not explicitly decorated with :func:`!runtime_checkable` @@ -2375,7 +2447,7 @@ New deprecations :class:`!webbrowser.MacOS` and scheduled for removal in Python 3.17. (Contributed by Jeff Lyon in :gh:`137586`.) -* ``__version__`` +* ``__version__``: * The ``__version__``, ``version`` and ``VERSION`` attributes have been deprecated in these standard library modules and will be removed in @@ -2488,8 +2560,7 @@ New features * :c:type:`PyCriticalSection` and related functions are added to the Stable ABI. - - (Contributed in :gh:`149227`.) + (Contributed by Petr Viktorin in :gh:`149225`.) * Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C API for creating a module from a *spec* and *initfunc*. @@ -2541,7 +2612,7 @@ New features ``PyModExport`` :ref:`module export hook ` also use the new :c:type:`!PySlot` struct. - These following functions are :term:`soft deprecated`: + The following functions are :term:`soft deprecated`: * :c:func:`PyType_FromSpec` * :c:func:`PyType_FromSpecWithBases` @@ -2551,7 +2622,6 @@ New features * :c:func:`PyModule_FromDefAndSpec2` * :c:func:`PyModule_ExecDef` - The slots :c:macro:`Py_tp_bases` and :c:macro:`Py_tp_base` are now equivalent: they can be set either to a single type or a tuple of types. The :c:macro:`Py_tp_bases` slot is preferred; the other is ignored if both @@ -2684,8 +2754,8 @@ Deprecated C APIs (Contributed by Victor Stinner in :gh:`129813`.) * :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and - :c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in - 3.20. Instead, use :c:func:`PyUnicode_InternFromString()` and cache the result in + :c:func:`!_PyUnicode_FromId` are deprecated since Python 3.15 and will be removed in + Python 3.20. Instead, use :c:func:`PyUnicode_InternFromString()` and cache the result in the module state, then call :c:func:`PyObject_CallMethod` or :c:func:`PyObject_GetAttr`. (Contributed by Victor Stinner in :gh:`141049`.) @@ -2703,7 +2773,7 @@ Deprecated C APIs (Contributed by Sergey B Kirpichev in :gh:`128813`.) * :c:member:`~PyConfig.bytes_warning` is deprecated - since 3.15 and will be removed in 3.17. + since Python 3.15 and will be removed in Python 3.17. (Contributed by Nikita Sobolev in :gh:`136355`.) * :c:macro:`!Py_INFINITY` macro is :term:`soft deprecated`, @@ -2729,7 +2799,7 @@ Deprecated C APIs (Contributed by Petr Viktorin in :gh:`146175`.) * :c:macro:`!Py_MATH_El` and :c:macro:`!Py_MATH_PIl` are deprecated - since 3.15 and will be removed in 3.20. + since Python 3.15 and will be removed in Python 3.20. (Contributed by Sergey B Kirpichev in :gh:`141004`.) @@ -2757,6 +2827,7 @@ Build changes On Windows, use ``build.bat --pymalloc-hugepages``. At runtime, huge pages must be explicitly enabled by setting the :envvar:`PYTHON_PYMALLOC_HUGEPAGES` environment variable to ``1``. + (Contributed by Pablo Galindo Salgado in :gh:`144319`.) * Annotating anonymous mmap usage is now supported if the Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME ` (Linux 5.17 or newer). @@ -2819,6 +2890,7 @@ that may require changes to your code. * :meth:`mmap.mmap.resize` has been removed on platforms that don't support the underlying syscall, instead of raising a :exc:`SystemError`. + (Contributed by An Long in :gh:`138205`.) * A resource warning is now emitted for an unclosed :func:`xml.etree.ElementTree.iterparse` iterator if it opened a file. From 21223c9ff592d65c362ffa62a9592fe87d07610a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 21 Sep 2026 10:32:18 +0200 Subject: [PATCH 5/7] gh-157710: Enable read-only optimization in PyUnicodeWriter (#157861) If the first and only write to a PyUnicodeWriter is a Python str object, PyUnicodeWriter_Finish() returns the object unchanged. PyUnicodeWriter_WriteChar() uses a singleton if no buffer was allocated yet. Move test_unicode_equal() to the correct test case (CAPITest). --- Include/internal/pycore_unicodeobject.h | 46 +++- Lib/test/test_capi/test_unicode.py | 291 +++++++++++++++--------- Objects/unicode_writer.c | 27 +-- Objects/unicodeobject.c | 2 + 4 files changed, 226 insertions(+), 140 deletions(-) diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index e9a4aed37030e76..ff3fda8583133e2 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -10,6 +10,7 @@ extern "C" { #include "pycore_fileutils.h" // _Py_error_handler #include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI +#include "pycore_runtime.h" // _Py_LATIN1_CHR() // Maximum code point of Unicode 6.0: 0x10ffff (1,114,111). @@ -111,10 +112,12 @@ _PyUnicode_EnsureUnicode(PyObject *obj) static inline int _PyUnicodeWriter_CanWrite(_PyUnicodeWriter *writer) { - // Code adapted from _PyUnicode_IsModifiable() assert(!writer->readonly); + PyObject *buffer = writer->buffer; assert(buffer != NULL); + + // Code adapted from _PyUnicode_IsModifiable(). // Do not use _PyObject_IsUniquelyReferenced(): the caller can have its own // lock to prevent a writer from being used by two threads at the same // time. @@ -126,13 +129,48 @@ _PyUnicodeWriter_CanWrite(_PyUnicodeWriter *writer) } #endif +static inline void +_PyUnicodeWriter_Update(_PyUnicodeWriter *writer) +{ + PyObject *buffer = writer->buffer; + writer->maxchar = PyUnicode_MAX_CHAR_VALUE(buffer); + writer->data = PyUnicode_DATA(buffer); + writer->kind = PyUnicode_KIND(buffer); + + if (!writer->readonly) { + writer->size = PyUnicode_GET_LENGTH(buffer); + } + else { + /* Copy-on-write mode: set buffer size to 0 so + * _PyUnicodeWriter_Prepare() will copy (and enlarge) the buffer on + * next write. */ + writer->size = 0; + } +} + static inline int _PyUnicodeWriter_WriteCharInline(_PyUnicodeWriter *writer, Py_UCS4 ch) { - assert(ch <= _Py_MAX_UNICODE); - if (_PyUnicodeWriter_Prepare(writer, 1, ch) < 0) - return -1; + if (ch > writer->maxchar || 1 > writer->size - writer->pos) { + if (writer->buffer == NULL && ch <= 255) { + // If the first write is a Latin1 character, use the singleton + // as a read-only object + PyObject *obj = _Py_LATIN1_CHR(ch); + writer->readonly = 1; + writer->buffer = obj; // Py_NewRef() is not need on immortal object + _PyUnicodeWriter_Update(writer); + assert(writer->pos == 0); + writer->pos = 1; + // The next write will create a new buffer and copy the string + return 0; + } + + if (_PyUnicodeWriter_PrepareInternal(writer, 1, ch) == -1) { + return -1; + } + } assert(_PyUnicodeWriter_CanWrite(writer)); + PyUnicode_WRITE(writer->kind, writer->data, writer->pos, ch); writer->pos++; return 0; diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index f13ad6f428ec095..9bfb148f87b585d 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -1817,6 +1817,39 @@ def test_is_compact_ascii(self): # CRASHES is_compact_ascii(NULL) + def test_unicode_equal(self): + unicode_equal = _testlimitedcapi.unicode_equal + + def copy(text): + return text.encode().decode() + + self.assertTrue(unicode_equal("", "")) + self.assertTrue(unicode_equal("abc", "abc")) + self.assertTrue(unicode_equal("abc", copy("abc"))) + self.assertTrue(unicode_equal("\u20ac", copy("\u20ac"))) + self.assertTrue(unicode_equal("\U0010ffff", copy("\U0010ffff"))) + + self.assertFalse(unicode_equal("abc", "abcd")) + self.assertFalse(unicode_equal("\u20ac", "\u20ad")) + self.assertFalse(unicode_equal("\U0010ffff", "\U0010fffe")) + + # str subclass + self.assertTrue(unicode_equal("abc", Str("abc"))) + self.assertTrue(unicode_equal(Str("abc"), "abc")) + self.assertFalse(unicode_equal("abc", Str("abcd"))) + self.assertFalse(unicode_equal(Str("abc"), "abcd")) + + # invalid type + for invalid_type in (b'bytes', 123, ("tuple",)): + with self.subTest(invalid_type=invalid_type): + with self.assertRaises(TypeError): + unicode_equal("abc", invalid_type) + with self.assertRaises(TypeError): + unicode_equal(invalid_type, "abc") + + # CRASHES unicode_equal("abc", NULL) + # CRASHES unicode_equal(NULL, "abc") + class PyUnicodeWriterTest(unittest.TestCase): def create_writer(self, size): @@ -1865,6 +1898,11 @@ def test_write_char(self): self.assertEqual(writer.finish(), "\0$\u20AC\U0010FFFF") + writer = self.create_writer(0) + for ch in 'hello': + writer.write_char(ord(ch)) + self.assertEqual(writer.finish(), 'hello') + def test_utf8(self): writer = self.create_writer(0) writer.write_utf8(b"ascii", -1) @@ -2057,14 +2095,25 @@ def test_singletons(self): writer.write_substring('text', 0, 0) self.assertIs(writer.finish(), '') - for ch in range(256): - with self.subTest(ch=ch): - ch = chr(ch) - writer = self.create_writer(0) - # Use PyUnicodeWriter_WriteSubstring() to avoid the read-only - # buffer optimization - writer.write_substring(ch + 'xxx', 0, 1) - self.assertIs(writer.finish(), ch) + for size in (0, 123): + for ch in range(256): + with self.subTest(size=size, ch=ch): + ch = chr(ch) + + # If the first write is a Latin1 character and no buffer + # was allocated yet, use the singleton as the read-only + # buffer + writer = self.create_writer(size) + writer.write_char(ord(ch)) + self.assertIs(writer.finish(), ch) + + # PyUnicodeWriter_Finish() replaces the buffer + # with the singleton + writer = self.create_writer(size) + # Use PyUnicodeWriter_WriteSubstring() to avoid + # the read-only buffer optimization + writer.write_substring(ch + 'xxx', 0, 1) + self.assertIs(writer.finish(), ch) @unittest.skipUnless(support.Py_DEBUG, 'need debug build (Py_DEBUG)') def test_detect_overflow(self): @@ -2115,6 +2164,32 @@ def test_change_kind(self): self.assertEqual(writer.finish(), 'ascii latin1:\xe9 ucs2:\u20ac ucs4:\U0010ffff') + def test_readonly_optim(self): + # Read-only optimization: if the first and only write is a Python str + # object and no buffer was allocated yet, return the object unchanged + unique_string = 'unique string' + writer = self.create_writer(0) + writer.write_str(unique_string) + self.assertIs(writer.finish(), unique_string) + + writer = self.create_writer(0) + writer.write_substring(unique_string, 0, len(unique_string)) + self.assertIs(writer.finish(), unique_string) + + class MyStr: + def __str__(self): + return unique_string + writer = self.create_writer(0) + writer.write_str(MyStr()) + self.assertIs(writer.finish(), unique_string) + + class MyRepr: + def __repr__(self): + return unique_string + writer = self.create_writer(0) + writer.write_repr(MyRepr()) + self.assertIs(writer.finish(), unique_string) + # Test PyUnicodeWriter_Format() @unittest.skipIf(ctypes is None, 'need ctypes') @@ -2152,112 +2227,106 @@ def test_recover_error(self): self.assertEqual(writer.finish(), 'Hello World.') - def test_unicode_equal(self): - unicode_equal = _testlimitedcapi.unicode_equal - - def copy(text): - return text.encode().decode() - - self.assertTrue(unicode_equal("", "")) - self.assertTrue(unicode_equal("abc", "abc")) - self.assertTrue(unicode_equal("abc", copy("abc"))) - self.assertTrue(unicode_equal("\u20ac", copy("\u20ac"))) - self.assertTrue(unicode_equal("\U0010ffff", copy("\U0010ffff"))) + def test_readonly_optim(self): + # Read-only optimization: if the first and only write is a Python str + # object and no buffer was allocated yet, return the object unchanged + from ctypes import py_object - self.assertFalse(unicode_equal("abc", "abcd")) - self.assertFalse(unicode_equal("\u20ac", "\u20ad")) - self.assertFalse(unicode_equal("\U0010ffff", "\U0010fffe")) - - # str subclass - self.assertTrue(unicode_equal("abc", Str("abc"))) - self.assertTrue(unicode_equal(Str("abc"), "abc")) - self.assertFalse(unicode_equal("abc", Str("abcd"))) - self.assertFalse(unicode_equal(Str("abc"), "abcd")) - - # invalid type - for invalid_type in (b'bytes', 123, ("tuple",)): - with self.subTest(invalid_type=invalid_type): - with self.assertRaises(TypeError): - unicode_equal("abc", invalid_type) - with self.assertRaises(TypeError): - unicode_equal(invalid_type, "abc") + unique_string = 'unique string' + for format in (b'%S', b'%U'): + with self.subTest(format=format): + writer = self.create_writer(0) + self.writer_format(writer, format, py_object(unique_string)) + self.assertIs(writer.finish(), unique_string) - # CRASHES unicode_equal("abc", NULL) - # CRASHES unicode_equal(NULL, "abc") + class MyStr: + def __str__(self): + return unique_string + writer = self.create_writer(0) + self.writer_format(writer, b'%S', py_object(MyStr())) + self.assertIs(writer.finish(), unique_string) - # TODO: Add tests to the following codec functions: - # - PyUnicode_AsASCIIString - # - PyUnicode_AsCharmapString - # - PyUnicode_AsEncodedString - # - PyUnicode_AsLatin1String - # - PyUnicode_AsMBCSString - # - PyUnicode_AsRawUnicodeEscapeString - # - PyUnicode_AsUTF16String - # - PyUnicode_AsUTF32String - # - PyUnicode_AsUTF8String - # - PyUnicode_AsUnicodeEscapeString - # - PyUnicode_BuildEncodingMap - # - PyUnicode_Decode - # - PyUnicode_DecodeASCII - # - PyUnicode_DecodeCharmap - # - PyUnicode_DecodeCodePageStateful - # - PyUnicode_DecodeFSDefault - # - PyUnicode_DecodeFSDefaultAndSize - # - PyUnicode_DecodeLatin1 - # - PyUnicode_DecodeLocale - # - PyUnicode_DecodeLocaleAndSize - # - PyUnicode_DecodeMBCS - # - PyUnicode_DecodeMBCSStateful - # - PyUnicode_DecodeRawUnicodeEscape - # - PyUnicode_DecodeUTF16 - # - PyUnicode_DecodeUTF16Stateful - # - PyUnicode_DecodeUTF32 - # - PyUnicode_DecodeUTF32Stateful - # - PyUnicode_DecodeUTF7 - # - PyUnicode_DecodeUTF7Stateful - # - PyUnicode_DecodeUTF8 - # - PyUnicode_DecodeUTF8Stateful - # - PyUnicode_DecodeUnicodeEscape - # - PyUnicode_EncodeCodePage - # - PyUnicode_EncodeFSDefault - # - PyUnicode_EncodeLocale - # - PyUnicode_FSConverter - # - PyUnicode_FSDecoder - # - PyUnicode_FromEncodedObject - # - PyUnicode_Splitlines - - # TODO: Add tests to the following character functions: - # - Py_UNICODE_ISALNUM - # - Py_UNICODE_ISALPHA - # - Py_UNICODE_ISDECIMAL - # - Py_UNICODE_ISDIGIT - # - Py_UNICODE_ISLINEBREAK - # - Py_UNICODE_ISLOWER - # - Py_UNICODE_ISNUMERIC - # - Py_UNICODE_ISPRINTABLE - # - Py_UNICODE_ISSPACE - # - Py_UNICODE_ISTITLE - # - Py_UNICODE_ISUPPER - # - Py_UNICODE_TODECIMAL - # - Py_UNICODE_TODIGIT - # - Py_UNICODE_TOLOWER - # - Py_UNICODE_TONUMERIC - # - Py_UNICODE_TOTITLE - # - Py_UNICODE_TOUPPER - - # TODO: Maybe add tests to the following less important functions: - # - PyUnicode_1BYTE_DATA - # - PyUnicode_2BYTE_DATA - # - PyUnicode_4BYTE_DATA - # - PyUnicode_DATA - # - PyUnicode_IS_READY - # - PyUnicode_READY - # - Py_UNICODE_HIGH_SURROGATE - # - Py_UNICODE_IS_HIGH_SURROGATE - # - Py_UNICODE_IS_LOW_SURROGATE - # - Py_UNICODE_IS_SURROGATE - # - Py_UNICODE_JOIN_SURROGATES - # - Py_UNICODE_LOW_SURROGATE + class MyRepr: + def __repr__(self): + return unique_string + writer = self.create_writer(0) + self.writer_format(writer, b'%R', py_object(MyRepr())) + self.assertIs(writer.finish(), unique_string) + + +# TODO: Add tests to the following codec functions: +# - PyUnicode_AsASCIIString +# - PyUnicode_AsCharmapString +# - PyUnicode_AsEncodedString +# - PyUnicode_AsLatin1String +# - PyUnicode_AsMBCSString +# - PyUnicode_AsRawUnicodeEscapeString +# - PyUnicode_AsUTF16String +# - PyUnicode_AsUTF32String +# - PyUnicode_AsUTF8String +# - PyUnicode_AsUnicodeEscapeString +# - PyUnicode_BuildEncodingMap +# - PyUnicode_Decode +# - PyUnicode_DecodeASCII +# - PyUnicode_DecodeCharmap +# - PyUnicode_DecodeCodePageStateful +# - PyUnicode_DecodeFSDefault +# - PyUnicode_DecodeFSDefaultAndSize +# - PyUnicode_DecodeLatin1 +# - PyUnicode_DecodeLocale +# - PyUnicode_DecodeLocaleAndSize +# - PyUnicode_DecodeMBCS +# - PyUnicode_DecodeMBCSStateful +# - PyUnicode_DecodeRawUnicodeEscape +# - PyUnicode_DecodeUTF16 +# - PyUnicode_DecodeUTF16Stateful +# - PyUnicode_DecodeUTF32 +# - PyUnicode_DecodeUTF32Stateful +# - PyUnicode_DecodeUTF7 +# - PyUnicode_DecodeUTF7Stateful +# - PyUnicode_DecodeUTF8 +# - PyUnicode_DecodeUTF8Stateful +# - PyUnicode_DecodeUnicodeEscape +# - PyUnicode_EncodeCodePage +# - PyUnicode_EncodeFSDefault +# - PyUnicode_EncodeLocale +# - PyUnicode_FSConverter +# - PyUnicode_FSDecoder +# - PyUnicode_FromEncodedObject +# - PyUnicode_Splitlines + +# TODO: Add tests to the following character functions: +# - Py_UNICODE_ISALNUM +# - Py_UNICODE_ISALPHA +# - Py_UNICODE_ISDECIMAL +# - Py_UNICODE_ISDIGIT +# - Py_UNICODE_ISLINEBREAK +# - Py_UNICODE_ISLOWER +# - Py_UNICODE_ISNUMERIC +# - Py_UNICODE_ISPRINTABLE +# - Py_UNICODE_ISSPACE +# - Py_UNICODE_ISTITLE +# - Py_UNICODE_ISUPPER +# - Py_UNICODE_TODECIMAL +# - Py_UNICODE_TODIGIT +# - Py_UNICODE_TOLOWER +# - Py_UNICODE_TONUMERIC +# - Py_UNICODE_TOTITLE +# - Py_UNICODE_TOUPPER + +# TODO: Maybe add tests to the following less important functions: +# - PyUnicode_1BYTE_DATA +# - PyUnicode_2BYTE_DATA +# - PyUnicode_4BYTE_DATA +# - PyUnicode_DATA +# - PyUnicode_IS_READY +# - PyUnicode_READY +# - Py_UNICODE_HIGH_SURROGATE +# - Py_UNICODE_IS_HIGH_SURROGATE +# - Py_UNICODE_IS_LOW_SURROGATE +# - Py_UNICODE_IS_SURROGATE +# - Py_UNICODE_JOIN_SURROGATES +# - Py_UNICODE_LOW_SURROGATE if __name__ == "__main__": diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c index 751fca9948598ff..fc3a95cd97e4213 100644 --- a/Objects/unicode_writer.c +++ b/Objects/unicode_writer.c @@ -115,30 +115,6 @@ unicode_write_cstr(PyObject *unicode, Py_ssize_t index, } -static inline void -_PyUnicodeWriter_Update(_PyUnicodeWriter *writer) -{ - writer->maxchar = PyUnicode_MAX_CHAR_VALUE(writer->buffer); - writer->data = PyUnicode_DATA(writer->buffer); - - if (!writer->readonly) { - writer->kind = PyUnicode_KIND(writer->buffer); - writer->size = PyUnicode_GET_LENGTH(writer->buffer); - } - else { - /* use a value smaller than PyUnicode_1BYTE_KIND() so - _PyUnicodeWriter_PrepareKind() will copy the buffer. */ - writer->kind = 0; - assert(writer->kind <= PyUnicode_1BYTE_KIND); - - /* Copy-on-write mode: set buffer size to 0 so - * _PyUnicodeWriter_Prepare() will copy (and enlarge) the buffer on - * next write. */ - writer->size = 0; - } -} - - void _PyUnicodeWriter_Init(_PyUnicodeWriter *writer) { @@ -342,12 +318,13 @@ _PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str) return 0; maxchar = PyUnicode_MAX_CHAR_VALUE(str); if (maxchar > writer->maxchar || len > writer->size - writer->pos) { - if (writer->buffer == NULL && !writer->overallocate) { + if (writer->buffer == NULL) { assert(_PyUnicode_CheckConsistency(str, 1)); writer->readonly = 1; writer->buffer = Py_NewRef(str); _PyUnicodeWriter_Update(writer); writer->pos += len; + // The next write will create a new buffer and copy the string return 0; } if (_PyUnicodeWriter_PrepareInternal(writer, len, maxchar) == -1) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 5d0b818981fc982..da9f8dcc223fc0d 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -5230,6 +5230,7 @@ unicode_decode_utf8_impl(_PyUnicodeWriter *writer, if (_PyUnicodeWriter_PrepareKind(writer, PyUnicode_2BYTE_KIND) < 0) goto onError; + assert(_PyUnicodeWriter_CanWrite(writer)); for (i=startinpos; ikind, writer->data, writer->pos, @@ -7472,6 +7473,7 @@ PyUnicode_DecodeASCII(const char *s, but we may switch to UCS2 at the first write */ if (_PyUnicodeWriter_PrepareKind(&writer, PyUnicode_2BYTE_KIND) < 0) goto onError; + assert(_PyUnicodeWriter_CanWrite(&writer)); kind = writer.kind; data = writer.data; From 1596e58f45a26f2a694d26a2b856064640b06608 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 21 Sep 2026 12:44:36 +0300 Subject: [PATCH 6/7] gh-155496: Use Argument Clinic for more functions in the time module (GH-155513) Co-authored-by: Claude Opus 5 (1M context) --- Lib/test/test_inspect/test_inspect.py | 13 +- .../test_unittest/testmock/testhelpers.py | 9 +- Modules/clinic/timemodule.c.h | 682 +++++++++++++++++- Modules/timemodule.c | 514 +++++++------ 4 files changed, 975 insertions(+), 243 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 25276fc40cb0287..a4381f2c0233954 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6378,15 +6378,10 @@ def test_thread_module_has_signatures(self): self._test_module_has_signatures(_thread, no_signature) def test_time_module_has_signatures(self): - no_signature = { - 'asctime', 'ctime', 'get_clock_info', 'gmtime', 'localtime', - 'strftime', 'strptime' - } - no_signature |= {name for name in - ['clock_getres', 'clock_settime', 'clock_settime_ns', - 'pthread_getcpuclockid'] - if hasattr(time, name)} - self._test_module_has_signatures(time, no_signature) + no_signature = {'strftime', 'strptime'} + unsupported_signature = {'asctime'} + self._test_module_has_signatures(time, no_signature, + unsupported_signature) def test_tokenize_module_has_signatures(self): import tokenize diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py index 0e82c723ec3eaa2..476e9105063bf99 100644 --- a/Lib/test/test_unittest/testmock/testhelpers.py +++ b/Lib/test/test_unittest/testmock/testhelpers.py @@ -1,8 +1,9 @@ import inspect -import time import types import unittest +from test.support import import_helper + from unittest.mock import ( call, _Call, create_autospec, MagicMock, Mock, ANY, _CallList, patch, PropertyMock, _callable @@ -929,8 +930,10 @@ def check_data_descriptor(mock_attr): def test_autospec_on_bound_builtin_function(self): - meth = types.MethodType(time.ctime, time.time()) - self.assertIsInstance(meth(), str) + _testcapi = import_helper.import_module('_testcapi') + # This function is defined without a signature. + meth = types.MethodType(_testcapi.docstring_no_signature, object()) + self.assertIsNone(meth()) mocked = create_autospec(meth) # no signature, so no spec to check against diff --git a/Modules/clinic/timemodule.c.h b/Modules/clinic/timemodule.c.h index bbc0748f9a9c0dd..3b0a8d44129d381 100644 --- a/Modules/clinic/timemodule.c.h +++ b/Modules/clinic/timemodule.c.h @@ -2,6 +2,57 @@ preserve [clinic start generated code]*/ +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(time_time__doc__, +"time($module, /)\n" +"--\n" +"\n" +"Return the current time in seconds since the Epoch.\n" +"\n" +"Fractions of a second may be present if the system clock provides\n" +"them."); + +#define TIME_TIME_METHODDEF \ + {"time", (PyCFunction)time_time, METH_NOARGS, time_time__doc__}, + +static double +time_time_impl(PyObject *module); + +static PyObject * +time_time(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + double _return_value; + + _return_value = time_time_impl(module); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_time_ns__doc__, +"time_ns($module, /)\n" +"--\n" +"\n" +"Return the current time in nanoseconds since the Epoch."); + +#define TIME_TIME_NS_METHODDEF \ + {"time_ns", (PyCFunction)time_time_ns, METH_NOARGS, time_time_ns__doc__}, + +static PyObject * +time_time_ns_impl(PyObject *module); + +static PyObject * +time_time_ns(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return time_time_ns_impl(module); +} + #if defined(HAVE_CLOCK_GETTIME) PyDoc_STRVAR(time_clock_gettime__doc__, @@ -64,6 +115,603 @@ time_clock_gettime_ns(PyObject *module, PyObject *arg) #endif /* defined(HAVE_CLOCK_GETTIME) */ +#if defined(HAVE_CLOCK_SETTIME) + +PyDoc_STRVAR(time_clock_settime__doc__, +"clock_settime($module, clk_id, time, /)\n" +"--\n" +"\n" +"Set the time of the specified clock clk_id."); + +#define TIME_CLOCK_SETTIME_METHODDEF \ + {"clock_settime", _PyCFunction_CAST(time_clock_settime), METH_FASTCALL, time_clock_settime__doc__}, + +static PyObject * +time_clock_settime_impl(PyObject *module, int clk_id, PyObject *obj); + +static PyObject * +time_clock_settime(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int clk_id; + PyObject *obj; + + if (!_PyArg_CheckPositional("clock_settime", nargs, 2, 2)) { + goto exit; + } + clk_id = PyLong_AsInt(args[0]); + if (clk_id == -1 && PyErr_Occurred()) { + goto exit; + } + obj = args[1]; + return_value = time_clock_settime_impl(module, clk_id, obj); + +exit: + return return_value; +} + +#endif /* defined(HAVE_CLOCK_SETTIME) */ + +#if defined(HAVE_CLOCK_SETTIME) + +PyDoc_STRVAR(time_clock_settime_ns__doc__, +"clock_settime_ns($module, clk_id, time, /)\n" +"--\n" +"\n" +"Set the time of the specified clock clk_id with nanoseconds."); + +#define TIME_CLOCK_SETTIME_NS_METHODDEF \ + {"clock_settime_ns", _PyCFunction_CAST(time_clock_settime_ns), METH_FASTCALL, time_clock_settime_ns__doc__}, + +static PyObject * +time_clock_settime_ns_impl(PyObject *module, int clk_id, PyObject *obj); + +static PyObject * +time_clock_settime_ns(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int clk_id; + PyObject *obj; + + if (!_PyArg_CheckPositional("clock_settime_ns", nargs, 2, 2)) { + goto exit; + } + clk_id = PyLong_AsInt(args[0]); + if (clk_id == -1 && PyErr_Occurred()) { + goto exit; + } + obj = args[1]; + return_value = time_clock_settime_ns_impl(module, clk_id, obj); + +exit: + return return_value; +} + +#endif /* defined(HAVE_CLOCK_SETTIME) */ + +#if defined(HAVE_CLOCK_GETRES) + +PyDoc_STRVAR(time_clock_getres__doc__, +"clock_getres($module, clk_id, /)\n" +"--\n" +"\n" +"Return the resolution (precision) of the specified clock clk_id."); + +#define TIME_CLOCK_GETRES_METHODDEF \ + {"clock_getres", (PyCFunction)time_clock_getres, METH_O, time_clock_getres__doc__}, + +static double +time_clock_getres_impl(PyObject *module, int clk_id); + +static PyObject * +time_clock_getres(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int clk_id; + double _return_value; + + clk_id = PyLong_AsInt(arg); + if (clk_id == -1 && PyErr_Occurred()) { + goto exit; + } + _return_value = time_clock_getres_impl(module, clk_id); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +#endif /* defined(HAVE_CLOCK_GETRES) */ + +#if defined(HAVE_PTHREAD_GETCPUCLOCKID) + +PyDoc_STRVAR(time_pthread_getcpuclockid__doc__, +"pthread_getcpuclockid($module, thread_id, /)\n" +"--\n" +"\n" +"Return the clk_id of a thread\'s CPU time clock."); + +#define TIME_PTHREAD_GETCPUCLOCKID_METHODDEF \ + {"pthread_getcpuclockid", (PyCFunction)time_pthread_getcpuclockid, METH_O, time_pthread_getcpuclockid__doc__}, + +static PyObject * +time_pthread_getcpuclockid_impl(PyObject *module, unsigned long thread_id); + +static PyObject * +time_pthread_getcpuclockid(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + unsigned long thread_id; + + if (!PyIndex_Check(arg)) { + _PyArg_BadArgument("pthread_getcpuclockid", "argument", "int", arg); + goto exit; + } + { + Py_ssize_t _bytes = PyLong_AsNativeBytes(arg, &thread_id, sizeof(unsigned long), + Py_ASNATIVEBYTES_NATIVE_ENDIAN | + Py_ASNATIVEBYTES_ALLOW_INDEX | + Py_ASNATIVEBYTES_UNSIGNED_BUFFER); + if (_bytes < 0) { + goto exit; + } + if ((size_t)_bytes > sizeof(unsigned long)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "integer value out of range", 1) < 0) + { + goto exit; + } + } + } + return_value = time_pthread_getcpuclockid_impl(module, thread_id); + +exit: + return return_value; +} + +#endif /* defined(HAVE_PTHREAD_GETCPUCLOCKID) */ + +PyDoc_STRVAR(time_sleep__doc__, +"sleep($module, seconds, /)\n" +"--\n" +"\n" +"Delay execution for a given number of seconds.\n" +"\n" +"The argument may be a floating-point number for subsecond precision."); + +#define TIME_SLEEP_METHODDEF \ + {"sleep", (PyCFunction)time_sleep, METH_O, time_sleep__doc__}, + +PyDoc_STRVAR(time_gmtime__doc__, +"gmtime($module, seconds=None, /)\n" +"--\n" +"\n" +"Convert seconds since the Epoch to a time tuple expressing UTC.\n" +"\n" +"That is, Greenwich Mean Time. When \'seconds\' is not passed in, convert\n" +"the current time instead.\n" +"\n" +"If the platform supports the tm_gmtoff and tm_zone, they are available\n" +"as attributes only."); + +#define TIME_GMTIME_METHODDEF \ + {"gmtime", _PyCFunction_CAST(time_gmtime), METH_FASTCALL, time_gmtime__doc__}, + +static PyObject * +time_gmtime_impl(PyObject *module, PyObject *ot); + +static PyObject * +time_gmtime(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *ot = Py_None; + + if (!_PyArg_CheckPositional("gmtime", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + ot = args[0]; +skip_optional: + return_value = time_gmtime_impl(module, ot); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_localtime__doc__, +"localtime($module, seconds=None, /)\n" +"--\n" +"\n" +"Convert seconds since the Epoch to a time tuple expressing local time.\n" +"\n" +"When \'seconds\' is not passed in, convert the current time instead."); + +#define TIME_LOCALTIME_METHODDEF \ + {"localtime", _PyCFunction_CAST(time_localtime), METH_FASTCALL, time_localtime__doc__}, + +static PyObject * +time_localtime_impl(PyObject *module, PyObject *ot); + +static PyObject * +time_localtime(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *ot = Py_None; + + if (!_PyArg_CheckPositional("localtime", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + ot = args[0]; +skip_optional: + return_value = time_localtime_impl(module, ot); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_asctime__doc__, +"asctime($module, time_tuple=, /)\n" +"--\n" +"\n" +"Convert a time tuple to a string, e.g. \'Sat Jun 06 16:26:11 1998\'.\n" +"\n" +"When the time tuple is not present, current time as returned by\n" +"localtime() is used."); + +#define TIME_ASCTIME_METHODDEF \ + {"asctime", _PyCFunction_CAST(time_asctime), METH_FASTCALL, time_asctime__doc__}, + +static PyObject * +time_asctime_impl(PyObject *module, PyObject *tup); + +static PyObject * +time_asctime(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *tup = NULL; + + if (!_PyArg_CheckPositional("asctime", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + tup = args[0]; +skip_optional: + return_value = time_asctime_impl(module, tup); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_ctime__doc__, +"ctime($module, seconds=None, /)\n" +"--\n" +"\n" +"Convert a time in seconds since the Epoch to a string in local time.\n" +"\n" +"This is equivalent to asctime(localtime(seconds)). When \'seconds\' is\n" +"not passed in, convert the current time instead."); + +#define TIME_CTIME_METHODDEF \ + {"ctime", _PyCFunction_CAST(time_ctime), METH_FASTCALL, time_ctime__doc__}, + +static PyObject * +time_ctime_impl(PyObject *module, PyObject *ot); + +static PyObject * +time_ctime(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *ot = Py_None; + + if (!_PyArg_CheckPositional("ctime", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + ot = args[0]; +skip_optional: + return_value = time_ctime_impl(module, ot); + +exit: + return return_value; +} + +#if defined(HAVE_MKTIME) + +PyDoc_STRVAR(time_mktime__doc__, +"mktime($module, time_tuple, /)\n" +"--\n" +"\n" +"Convert a time tuple in local time to seconds since the Epoch.\n" +"\n" +"Note that mktime(gmtime(0)) will not generally return zero for most\n" +"time zones; instead the returned value will either be equal to that of\n" +"the timezone or altzone attributes on the time module."); + +#define TIME_MKTIME_METHODDEF \ + {"mktime", (PyCFunction)time_mktime, METH_O, time_mktime__doc__}, + +#endif /* defined(HAVE_MKTIME) */ + +#if defined(HAVE_WORKING_TZSET) + +PyDoc_STRVAR(time_tzset__doc__, +"tzset($module, /)\n" +"--\n" +"\n" +"Initialize, or reinitialize, the local timezone.\n" +"\n" +"The local timezone is set to the value stored in os.environ[\'TZ\']. The\n" +"TZ environment variable should be specified in standard Unix timezone\n" +"format as documented in the tzset man page (eg. \'US/Eastern\',\n" +"\'Europe/Amsterdam\'). Unknown timezones will silently fall back to UTC.\n" +"If the TZ environment variable is not set, the local timezone is set to\n" +"the systems best guess of wallclock time. Changing the TZ environment\n" +"variable without calling tzset *may* change the local timezone used by\n" +"methods such as localtime, but this behaviour should not be relied on."); + +#define TIME_TZSET_METHODDEF \ + {"tzset", (PyCFunction)time_tzset, METH_NOARGS, time_tzset__doc__}, + +static PyObject * +time_tzset_impl(PyObject *module); + +static PyObject * +time_tzset(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return time_tzset_impl(module); +} + +#endif /* defined(HAVE_WORKING_TZSET) */ + +PyDoc_STRVAR(time_monotonic__doc__, +"monotonic($module, /)\n" +"--\n" +"\n" +"Monotonic clock, cannot go backward."); + +#define TIME_MONOTONIC_METHODDEF \ + {"monotonic", (PyCFunction)time_monotonic, METH_NOARGS, time_monotonic__doc__}, + +static double +time_monotonic_impl(PyObject *module); + +static PyObject * +time_monotonic(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + double _return_value; + + _return_value = time_monotonic_impl(module); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_monotonic_ns__doc__, +"monotonic_ns($module, /)\n" +"--\n" +"\n" +"Monotonic clock, cannot go backward, as nanoseconds."); + +#define TIME_MONOTONIC_NS_METHODDEF \ + {"monotonic_ns", (PyCFunction)time_monotonic_ns, METH_NOARGS, time_monotonic_ns__doc__}, + +static PyObject * +time_monotonic_ns_impl(PyObject *module); + +static PyObject * +time_monotonic_ns(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return time_monotonic_ns_impl(module); +} + +PyDoc_STRVAR(time_perf_counter__doc__, +"perf_counter($module, /)\n" +"--\n" +"\n" +"Performance counter for benchmarking."); + +#define TIME_PERF_COUNTER_METHODDEF \ + {"perf_counter", (PyCFunction)time_perf_counter, METH_NOARGS, time_perf_counter__doc__}, + +static double +time_perf_counter_impl(PyObject *module); + +static PyObject * +time_perf_counter(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + double _return_value; + + _return_value = time_perf_counter_impl(module); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_perf_counter_ns__doc__, +"perf_counter_ns($module, /)\n" +"--\n" +"\n" +"Performance counter for benchmarking as nanoseconds."); + +#define TIME_PERF_COUNTER_NS_METHODDEF \ + {"perf_counter_ns", (PyCFunction)time_perf_counter_ns, METH_NOARGS, time_perf_counter_ns__doc__}, + +static PyObject * +time_perf_counter_ns_impl(PyObject *module); + +static PyObject * +time_perf_counter_ns(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return time_perf_counter_ns_impl(module); +} + +PyDoc_STRVAR(time_process_time__doc__, +"process_time($module, /)\n" +"--\n" +"\n" +"Process time for profiling.\n" +"\n" +"That is the sum of the kernel and user-space CPU time."); + +#define TIME_PROCESS_TIME_METHODDEF \ + {"process_time", (PyCFunction)time_process_time, METH_NOARGS, time_process_time__doc__}, + +static double +time_process_time_impl(PyObject *module); + +static PyObject * +time_process_time(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + double _return_value; + + _return_value = time_process_time_impl(module); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(time_process_time_ns__doc__, +"process_time_ns($module, /)\n" +"--\n" +"\n" +"Process time for profiling as nanoseconds.\n" +"\n" +"That is the sum of the kernel and user-space CPU time."); + +#define TIME_PROCESS_TIME_NS_METHODDEF \ + {"process_time_ns", (PyCFunction)time_process_time_ns, METH_NOARGS, time_process_time_ns__doc__}, + +static PyObject * +time_process_time_ns_impl(PyObject *module); + +static PyObject * +time_process_time_ns(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return time_process_time_ns_impl(module); +} + +#if defined(HAVE_THREAD_TIME) + +PyDoc_STRVAR(time_thread_time__doc__, +"thread_time($module, /)\n" +"--\n" +"\n" +"Thread time for profiling.\n" +"\n" +"That is the sum of the kernel and user-space CPU time."); + +#define TIME_THREAD_TIME_METHODDEF \ + {"thread_time", (PyCFunction)time_thread_time, METH_NOARGS, time_thread_time__doc__}, + +static double +time_thread_time_impl(PyObject *module); + +static PyObject * +time_thread_time(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + double _return_value; + + _return_value = time_thread_time_impl(module); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +#endif /* defined(HAVE_THREAD_TIME) */ + +#if defined(HAVE_THREAD_TIME) + +PyDoc_STRVAR(time_thread_time_ns__doc__, +"thread_time_ns($module, /)\n" +"--\n" +"\n" +"Thread time for profiling as nanoseconds.\n" +"\n" +"That is the sum of the kernel and user-space CPU time."); + +#define TIME_THREAD_TIME_NS_METHODDEF \ + {"thread_time_ns", (PyCFunction)time_thread_time_ns, METH_NOARGS, time_thread_time_ns__doc__}, + +static PyObject * +time_thread_time_ns_impl(PyObject *module); + +static PyObject * +time_thread_time_ns(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return time_thread_time_ns_impl(module); +} + +#endif /* defined(HAVE_THREAD_TIME) */ + +PyDoc_STRVAR(time_get_clock_info__doc__, +"get_clock_info($module, name, /)\n" +"--\n" +"\n" +"Get information of the specified clock."); + +#define TIME_GET_CLOCK_INFO_METHODDEF \ + {"get_clock_info", (PyCFunction)time_get_clock_info, METH_O, time_get_clock_info__doc__}, + +static PyObject * +time_get_clock_info_impl(PyObject *module, const char *name); + +static PyObject * +time_get_clock_info(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *name; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("get_clock_info", "argument", "str", arg); + goto exit; + } + Py_ssize_t name_length; + name = PyUnicode_AsUTF8AndSize(arg, &name_length); + if (name == NULL) { + goto exit; + } + if (strlen(name) != (size_t)name_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + return_value = time_get_clock_info_impl(module, name); + +exit: + return return_value; +} + #ifndef TIME_CLOCK_GETTIME_METHODDEF #define TIME_CLOCK_GETTIME_METHODDEF #endif /* !defined(TIME_CLOCK_GETTIME_METHODDEF) */ @@ -71,4 +719,36 @@ time_clock_gettime_ns(PyObject *module, PyObject *arg) #ifndef TIME_CLOCK_GETTIME_NS_METHODDEF #define TIME_CLOCK_GETTIME_NS_METHODDEF #endif /* !defined(TIME_CLOCK_GETTIME_NS_METHODDEF) */ -/*[clinic end generated code: output=b589a2132aa9df47 input=a9049054013a1b77]*/ + +#ifndef TIME_CLOCK_SETTIME_METHODDEF + #define TIME_CLOCK_SETTIME_METHODDEF +#endif /* !defined(TIME_CLOCK_SETTIME_METHODDEF) */ + +#ifndef TIME_CLOCK_SETTIME_NS_METHODDEF + #define TIME_CLOCK_SETTIME_NS_METHODDEF +#endif /* !defined(TIME_CLOCK_SETTIME_NS_METHODDEF) */ + +#ifndef TIME_CLOCK_GETRES_METHODDEF + #define TIME_CLOCK_GETRES_METHODDEF +#endif /* !defined(TIME_CLOCK_GETRES_METHODDEF) */ + +#ifndef TIME_PTHREAD_GETCPUCLOCKID_METHODDEF + #define TIME_PTHREAD_GETCPUCLOCKID_METHODDEF +#endif /* !defined(TIME_PTHREAD_GETCPUCLOCKID_METHODDEF) */ + +#ifndef TIME_MKTIME_METHODDEF + #define TIME_MKTIME_METHODDEF +#endif /* !defined(TIME_MKTIME_METHODDEF) */ + +#ifndef TIME_TZSET_METHODDEF + #define TIME_TZSET_METHODDEF +#endif /* !defined(TIME_TZSET_METHODDEF) */ + +#ifndef TIME_THREAD_TIME_METHODDEF + #define TIME_THREAD_TIME_METHODDEF +#endif /* !defined(TIME_THREAD_TIME_METHODDEF) */ + +#ifndef TIME_THREAD_TIME_NS_METHODDEF + #define TIME_THREAD_TIME_NS_METHODDEF +#endif /* !defined(TIME_THREAD_TIME_NS_METHODDEF) */ +/*[clinic end generated code: output=540a094cac69e0d4 input=a9049054013a1b77]*/ diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 0005974b52499ce..98db74c2222f204 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -96,33 +96,36 @@ get_time_state(PyObject *module) } -static PyObject* -_PyFloat_FromPyTime(PyTime_t t) -{ - double d = PyTime_AsSecondsDouble(t); - return PyFloat_FromDouble(d); -} +/*[clinic input] +time.time -> double +Return the current time in seconds since the Epoch. -static PyObject * -time_time(PyObject *self, PyObject *unused) +Fractions of a second may be present if the system clock provides +them. +[clinic start generated code]*/ + +static double +time_time_impl(PyObject *module) +/*[clinic end generated code: output=4adfc457b48923ca input=eea2e80c63bbcaad]*/ { PyTime_t t; if (PyTime_Time(&t) < 0) { - return NULL; + return -1.0; } - return _PyFloat_FromPyTime(t); + return PyTime_AsSecondsDouble(t); } -PyDoc_STRVAR(time_doc, -"time() -> floating-point number\n\ -\n\ -Return the current time in seconds since the Epoch.\n\ -Fractions of a second may be present if the system clock provides them."); +/*[clinic input] +time.time_ns + +Return the current time in nanoseconds since the Epoch. +[clinic start generated code]*/ static PyObject * -time_time_ns(PyObject *self, PyObject *unused) +time_time_ns_impl(PyObject *module) +/*[clinic end generated code: output=f5f1924ebdcf1cf3 input=3acccd9786731be4]*/ { PyTime_t t; if (PyTime_Time(&t) < 0) { @@ -131,11 +134,6 @@ time_time_ns(PyObject *self, PyObject *unused) return PyLong_FromInt64(t); } -PyDoc_STRVAR(time_ns_doc, -"time_ns() -> int\n\ -\n\ -Return the current time in nanoseconds since the Epoch."); - #ifdef HAVE_CLOCK #ifndef CLOCKS_PER_SEC @@ -266,18 +264,24 @@ time_clock_gettime_ns_impl(PyObject *module, clockid_t clk_id) #endif /* HAVE_CLOCK_GETTIME */ #ifdef HAVE_CLOCK_SETTIME +/*[clinic input] +time.clock_settime + + clk_id: int + time as obj: object + / + +Set the time of the specified clock clk_id. +[clinic start generated code]*/ + static PyObject * -time_clock_settime(PyObject *self, PyObject *args) +time_clock_settime_impl(PyObject *module, int clk_id, PyObject *obj) +/*[clinic end generated code: output=ff2fc2e129f5fdea input=0e71daa237ff6edc]*/ { - int clk_id; - PyObject *obj; PyTime_t t; struct timespec tp; int ret; - if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj)) - return NULL; - if (_PyTime_FromSecondsObject(&t, obj, _PyTime_ROUND_FLOOR) < 0) return NULL; @@ -292,24 +296,24 @@ time_clock_settime(PyObject *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(clock_settime_doc, -"clock_settime(clk_id, time)\n\ -\n\ -Set the time of the specified clock clk_id."); +/*[clinic input] +time.clock_settime_ns + + clk_id: int + time as obj: object + / + +Set the time of the specified clock clk_id with nanoseconds. +[clinic start generated code]*/ static PyObject * -time_clock_settime_ns(PyObject *self, PyObject *args) +time_clock_settime_ns_impl(PyObject *module, int clk_id, PyObject *obj) +/*[clinic end generated code: output=5d40ca0217bfe058 input=f2333aae32a7f441]*/ { - int clk_id; - PyObject *obj; PyTime_t t; struct timespec ts; int ret; - if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj)) { - return NULL; - } - if (PyLong_AsInt64(obj, &t) < 0) { return NULL; } @@ -325,37 +329,34 @@ time_clock_settime_ns(PyObject *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(clock_settime_ns_doc, -"clock_settime_ns(clk_id, time)\n\ -\n\ -Set the time of the specified clock clk_id with nanoseconds."); #endif /* HAVE_CLOCK_SETTIME */ #ifdef HAVE_CLOCK_GETRES -static PyObject * -time_clock_getres(PyObject *self, PyObject *args) +/*[clinic input] +time.clock_getres -> double + + clk_id: int + / + +Return the resolution (precision) of the specified clock clk_id. +[clinic start generated code]*/ + +static double +time_clock_getres_impl(PyObject *module, int clk_id) +/*[clinic end generated code: output=94c4fb7df9f0c2f2 input=d1409a8b5b3be180]*/ { int ret; - int clk_id; struct timespec tp; - if (!PyArg_ParseTuple(args, "i:clock_getres", &clk_id)) - return NULL; - ret = clock_getres((clockid_t)clk_id, &tp); if (ret != 0) { PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return -1.0; } - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + return tp.tv_sec + tp.tv_nsec * 1e-9; } -PyDoc_STRVAR(clock_getres_doc, -"clock_getres(clk_id) -> floating-point number\n\ -\n\ -Return the resolution (precision) of the specified clock clk_id."); - #ifdef __APPLE__ #pragma clang diagnostic pop #endif @@ -363,15 +364,21 @@ Return the resolution (precision) of the specified clock clk_id."); #endif /* HAVE_CLOCK_GETRES */ #ifdef HAVE_PTHREAD_GETCPUCLOCKID +/*[clinic input] +time.pthread_getcpuclockid + + thread_id: unsigned_long(bitwise=True) + / + +Return the clk_id of a thread's CPU time clock. +[clinic start generated code]*/ + static PyObject * -time_pthread_getcpuclockid(PyObject *self, PyObject *args) +time_pthread_getcpuclockid_impl(PyObject *module, unsigned long thread_id) +/*[clinic end generated code: output=4fc7d4cb73d2e894 input=eb8af7bbcf189270]*/ { - unsigned long thread_id; int err; clockid_t clk_id; - if (!PyArg_ParseTuple(args, "k:pthread_getcpuclockid", &thread_id)) { - return NULL; - } err = pthread_getcpuclockid((pthread_t)thread_id, &clk_id); if (err) { errno = err; @@ -384,14 +391,22 @@ time_pthread_getcpuclockid(PyObject *self, PyObject *args) return PyLong_FromLong(clk_id); } -PyDoc_STRVAR(pthread_getcpuclockid_doc, -"pthread_getcpuclockid(thread_id) -> int\n\ -\n\ -Return the clk_id of a thread's CPU time clock."); #endif /* HAVE_PTHREAD_GETCPUCLOCKID */ +/*[clinic input] +time.sleep + + seconds as timeout_obj: object + / + +Delay execution for a given number of seconds. + +The argument may be a floating-point number for subsecond precision. +[clinic start generated code]*/ + static PyObject * -time_sleep(PyObject *self, PyObject *timeout_obj) +time_sleep(PyObject *module, PyObject *timeout_obj) +/*[clinic end generated code: output=f46e88f5f5756f65 input=3928b1704a2faa12]*/ { if (PySys_Audit("time.sleep", "O", timeout_obj) < 0) { return NULL; @@ -411,12 +426,6 @@ time_sleep(PyObject *self, PyObject *timeout_obj) Py_RETURN_NONE; } -PyDoc_STRVAR(sleep_doc, -"sleep(seconds)\n\ -\n\ -Delay execution for a given number of seconds. The argument may be\n\ -a floating-point number for subsecond precision."); - static PyStructSequence_Field struct_time_type_fields[] = { {"tm_year", "year, for example, 1993"}, {"tm_mon", "month of year, range [1, 12]"}, @@ -500,19 +509,16 @@ tmtotuple(time_module_state *state, struct tm *p return v; } -/* Parse arg tuple that can contain an optional float-or-None value; - format needs to be "|O:name". +/* Convert a number of seconds since the Epoch, or None which means the + current time, to time_t. Returns non-zero on success (parallels PyArg_ParseTuple). */ static int -parse_time_t_args(PyObject *args, const char *format, time_t *pwhen) +parse_time_t_arg(PyObject *ot, time_t *pwhen) { - PyObject *ot = NULL; time_t whent; - if (!PyArg_ParseTuple(args, format, &ot)) - return 0; - if (ot == NULL || ot == Py_None) { + if (ot == Py_None) { whent = time(NULL); } else { @@ -523,13 +529,29 @@ parse_time_t_args(PyObject *args, const char *format, time_t *pwhen) return 1; } +/*[clinic input] +time.gmtime + + seconds as ot: object = None + / + +Convert seconds since the Epoch to a time tuple expressing UTC. + +That is, Greenwich Mean Time. When 'seconds' is not passed in, convert +the current time instead. + +If the platform supports the tm_gmtoff and tm_zone, they are available +as attributes only. +[clinic start generated code]*/ + static PyObject * -time_gmtime(PyObject *module, PyObject *args) +time_gmtime_impl(PyObject *module, PyObject *ot) +/*[clinic end generated code: output=375372dd236a6ed6 input=784de5b57649d4d7]*/ { time_t when; struct tm buf; - if (!parse_time_t_args(args, "|O:gmtime", &when)) + if (!parse_time_t_arg(ot, &when)) return NULL; errno = 0; @@ -557,23 +579,25 @@ timegm(struct tm *p) } #endif -PyDoc_STRVAR(gmtime_doc, -"gmtime([seconds]) -> (tm_year, tm_mon, tm_mday, tm_hour, tm_min,\n\ - tm_sec, tm_wday, tm_yday, tm_isdst)\n\ -\n\ -Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.\n\ -GMT). When 'seconds' is not passed in, convert the current time instead.\n\ -\n\ -If the platform supports the tm_gmtoff and tm_zone, they are available as\n\ -attributes only."); +/*[clinic input] +time.localtime + + seconds as ot: object = None + / + +Convert seconds since the Epoch to a time tuple expressing local time. + +When 'seconds' is not passed in, convert the current time instead. +[clinic start generated code]*/ static PyObject * -time_localtime(PyObject *module, PyObject *args) +time_localtime_impl(PyObject *module, PyObject *ot) +/*[clinic end generated code: output=d3c1c6818abd34a1 input=43b4e8bf4914300e]*/ { time_t when; struct tm buf; - if (!parse_time_t_args(args, "|O:localtime", &when)) + if (!parse_time_t_arg(ot, &when)) return NULL; if (_PyTime_localtime(when, &buf) != 0) return NULL; @@ -597,13 +621,6 @@ time_localtime(PyObject *module, PyObject *args) static const char *utc_string = NULL; #endif -PyDoc_STRVAR(localtime_doc, -"localtime([seconds]) -> (tm_year,tm_mon,tm_mday,tm_hour,tm_min,\n\ - tm_sec,tm_wday,tm_yday,tm_isdst)\n\ -\n\ -Convert seconds since the Epoch to a time tuple expressing local time.\n\ -When 'seconds' is not passed in, convert the current time instead."); - /* Convert 9-item tuple to tm structure. Return 1 on success, set * an exception and return 0 on error. */ @@ -1031,15 +1048,24 @@ _asctime(struct tm *timeptr) 1900 + timeptr->tm_year); } +/*[clinic input] +time.asctime + + time_tuple as tup: object = NULL + / + +Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'. + +When the time tuple is not present, current time as returned by +localtime() is used. +[clinic start generated code]*/ + static PyObject * -time_asctime(PyObject *module, PyObject *args) +time_asctime_impl(PyObject *module, PyObject *tup) +/*[clinic end generated code: output=a1bc45f84a00fb55 input=083c132f3cb23f1e]*/ { - PyObject *tup = NULL; struct tm buf; - if (!PyArg_UnpackTuple(args, "asctime", 0, 1, &tup)) - return NULL; - time_module_state *state = get_time_state(module); if (tup == NULL) { time_t tt = time(NULL); @@ -1055,35 +1081,48 @@ time_asctime(PyObject *module, PyObject *args) return _asctime(&buf); } -PyDoc_STRVAR(asctime_doc, -"asctime([time_tuple]) -> string\n\ -\n\ -Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.\n\ -When the time tuple is not present, current time as returned by localtime()\n\ -is used."); +/*[clinic input] +time.ctime + + seconds as ot: object = None + / + +Convert a time in seconds since the Epoch to a string in local time. + +This is equivalent to asctime(localtime(seconds)). When 'seconds' is +not passed in, convert the current time instead. +[clinic start generated code]*/ static PyObject * -time_ctime(PyObject *self, PyObject *args) +time_ctime_impl(PyObject *module, PyObject *ot) +/*[clinic end generated code: output=c3a028f5c6931cbc input=ee744f25ce87d1ae]*/ { time_t tt; struct tm buf; - if (!parse_time_t_args(args, "|O:ctime", &tt)) + if (!parse_time_t_arg(ot, &tt)) return NULL; if (_PyTime_localtime(tt, &buf) != 0) return NULL; return _asctime(&buf); } -PyDoc_STRVAR(ctime_doc, -"ctime([seconds]) -> string\n\ -\n\ -Convert a time in seconds since the Epoch to a string in local time.\n\ -This is equivalent to asctime(localtime(seconds)). When 'seconds' is not\n\ -passed in, convert the current time instead."); - #ifdef HAVE_MKTIME +/*[clinic input] +time.mktime + + time_tuple as tm_tuple: object + / + +Convert a time tuple in local time to seconds since the Epoch. + +Note that mktime(gmtime(0)) will not generally return zero for most +time zones; instead the returned value will either be equal to that of +the timezone or altzone attributes on the time module. +[clinic start generated code]*/ + static PyObject * time_mktime(PyObject *module, PyObject *tm_tuple) +/*[clinic end generated code: output=1b2a224cd309deb7 input=70b93e1c2e57e14e]*/ { struct tm tm; time_t tt; @@ -1152,20 +1191,29 @@ time_mktime(PyObject *module, PyObject *tm_tuple) return PyFloat_FromDouble((double)tt); } -PyDoc_STRVAR(mktime_doc, -"mktime(time_tuple) -> floating-point number\n\ -\n\ -Convert a time tuple in local time to seconds since the Epoch.\n\ -Note that mktime(gmtime(0)) will not generally return zero for most\n\ -time zones; instead the returned value will either be equal to that\n\ -of the timezone or altzone attributes on the time module."); #endif /* HAVE_MKTIME */ #ifdef HAVE_WORKING_TZSET static int init_timezone(PyObject *module); +/*[clinic input] +time.tzset + +Initialize, or reinitialize, the local timezone. + +The local timezone is set to the value stored in os.environ['TZ']. The +TZ environment variable should be specified in standard Unix timezone +format as documented in the tzset man page (eg. 'US/Eastern', +'Europe/Amsterdam'). Unknown timezones will silently fall back to UTC. +If the TZ environment variable is not set, the local timezone is set to +the systems best guess of wallclock time. Changing the TZ environment +variable without calling tzset *may* change the local timezone used by +methods such as localtime, but this behaviour should not be relied on. +[clinic start generated code]*/ + static PyObject * -time_tzset(PyObject *self, PyObject *unused) +time_tzset_impl(PyObject *module) +/*[clinic end generated code: output=d1564ac4d48d320b input=a4d11aab49badf88]*/ { PyObject* m; @@ -1190,38 +1238,35 @@ time_tzset(PyObject *self, PyObject *unused) Py_RETURN_NONE; } -PyDoc_STRVAR(tzset_doc, -"tzset()\n\ -\n\ -Initialize, or reinitialize, the local timezone to the value stored in\n\ -os.environ['TZ']. The TZ environment variable should be specified in\n\ -standard Unix timezone format as documented in the tzset man page\n\ -(eg. 'US/Eastern', 'Europe/Amsterdam'). Unknown timezones will silently\n\ -fall back to UTC. If the TZ environment variable is not set, the local\n\ -timezone is set to the systems best guess of wallclock time.\n\ -Changing the TZ environment variable without calling tzset *may* change\n\ -the local timezone used by methods such as localtime, but this behaviour\n\ -should not be relied on."); #endif /* HAVE_WORKING_TZSET */ -static PyObject * -time_monotonic(PyObject *self, PyObject *unused) +/*[clinic input] +time.monotonic -> double + +Monotonic clock, cannot go backward. +[clinic start generated code]*/ + +static double +time_monotonic_impl(PyObject *module) +/*[clinic end generated code: output=ab51899a17e16542 input=059cb42b2bad6df0]*/ { PyTime_t t; if (PyTime_Monotonic(&t) < 0) { - return NULL; + return -1.0; } - return _PyFloat_FromPyTime(t); + return PyTime_AsSecondsDouble(t); } -PyDoc_STRVAR(monotonic_doc, -"monotonic() -> float\n\ -\n\ -Monotonic clock, cannot go backward."); +/*[clinic input] +time.monotonic_ns + +Monotonic clock, cannot go backward, as nanoseconds. +[clinic start generated code]*/ static PyObject * -time_monotonic_ns(PyObject *self, PyObject *unused) +time_monotonic_ns_impl(PyObject *module) +/*[clinic end generated code: output=57a9261f91740349 input=14032d6b1601a300]*/ { PyTime_t t; if (PyTime_Monotonic(&t) < 0) { @@ -1230,30 +1275,32 @@ time_monotonic_ns(PyObject *self, PyObject *unused) return PyLong_FromInt64(t); } -PyDoc_STRVAR(monotonic_ns_doc, -"monotonic_ns() -> int\n\ -\n\ -Monotonic clock, cannot go backward, as nanoseconds."); +/*[clinic input] +time.perf_counter -> double +Performance counter for benchmarking. +[clinic start generated code]*/ -static PyObject * -time_perf_counter(PyObject *self, PyObject *unused) +static double +time_perf_counter_impl(PyObject *module) +/*[clinic end generated code: output=6eee280ca7b73cb1 input=f786239c5015893b]*/ { PyTime_t t; if (PyTime_PerfCounter(&t) < 0) { - return NULL; + return -1.0; } - return _PyFloat_FromPyTime(t); + return PyTime_AsSecondsDouble(t); } -PyDoc_STRVAR(perf_counter_doc, -"perf_counter() -> float\n\ -\n\ -Performance counter for benchmarking."); +/*[clinic input] +time.perf_counter_ns +Performance counter for benchmarking as nanoseconds. +[clinic start generated code]*/ static PyObject * -time_perf_counter_ns(PyObject *self, PyObject *unused) +time_perf_counter_ns_impl(PyObject *module) +/*[clinic end generated code: output=e11a728338108d42 input=178bf260d7d48a02]*/ { PyTime_t t; if (PyTime_PerfCounter(&t) < 0) { @@ -1262,12 +1309,6 @@ time_perf_counter_ns(PyObject *self, PyObject *unused) return PyLong_FromInt64(t); } -PyDoc_STRVAR(perf_counter_ns_doc, -"perf_counter_ns() -> int\n\ -\n\ -Performance counter for benchmarking as nanoseconds."); - - // gh-115714: Don't use times() on WASI. #if defined(HAVE_TIMES) && !defined(__wasi__) static int @@ -1424,24 +1465,37 @@ py_process_time(time_module_state *state, PyTime_t *tp, #endif } -static PyObject * -time_process_time(PyObject *module, PyObject *unused) +/*[clinic input] +time.process_time -> double + +Process time for profiling. + +That is the sum of the kernel and user-space CPU time. +[clinic start generated code]*/ + +static double +time_process_time_impl(PyObject *module) +/*[clinic end generated code: output=13b593cb16d415a8 input=6d539ae686c37c06]*/ { time_module_state *state = get_time_state(module); PyTime_t t; if (py_process_time(state, &t, NULL) < 0) { - return NULL; + return -1.0; } - return _PyFloat_FromPyTime(t); + return PyTime_AsSecondsDouble(t); } -PyDoc_STRVAR(process_time_doc, -"process_time() -> float\n\ -\n\ -Process time for profiling: sum of the kernel and user-space CPU time."); +/*[clinic input] +time.process_time_ns + +Process time for profiling as nanoseconds. + +That is the sum of the kernel and user-space CPU time. +[clinic start generated code]*/ static PyObject * -time_process_time_ns(PyObject *module, PyObject *unused) +time_process_time_ns_impl(PyObject *module) +/*[clinic end generated code: output=857f0c20105c4d1a input=6d998fd7a213c0cd]*/ { time_module_state *state = get_time_state(module); PyTime_t t; @@ -1451,13 +1505,6 @@ time_process_time_ns(PyObject *module, PyObject *unused) return PyLong_FromInt64(t); } -PyDoc_STRVAR(process_time_ns_doc, -"process_time() -> int\n\ -\n\ -Process time for profiling as nanoseconds:\n\ -sum of the kernel and user-space CPU time."); - - #if defined(MS_WINDOWS) #define HAVE_THREAD_TIME static int @@ -1599,23 +1646,36 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info) #pragma clang diagnostic ignored "-Wunguarded-availability" #endif -static PyObject * -time_thread_time(PyObject *self, PyObject *unused) +/*[clinic input] +time.thread_time -> double + +Thread time for profiling. + +That is the sum of the kernel and user-space CPU time. +[clinic start generated code]*/ + +static double +time_thread_time_impl(PyObject *module) +/*[clinic end generated code: output=33f6639edb42e8f5 input=cdf4621822b8ed60]*/ { PyTime_t t; if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) { - return NULL; + return -1.0; } - return _PyFloat_FromPyTime(t); + return PyTime_AsSecondsDouble(t); } -PyDoc_STRVAR(thread_time_doc, -"thread_time() -> float\n\ -\n\ -Thread time for profiling: sum of the kernel and user-space CPU time."); +/*[clinic input] +time.thread_time_ns + +Thread time for profiling as nanoseconds. + +That is the sum of the kernel and user-space CPU time. +[clinic start generated code]*/ static PyObject * -time_thread_time_ns(PyObject *self, PyObject *unused) +time_thread_time_ns_impl(PyObject *module) +/*[clinic end generated code: output=2a1ac9cc3e1d4c37 input=a79b2b91f308277d]*/ { PyTime_t t; if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) { @@ -1624,12 +1684,6 @@ time_thread_time_ns(PyObject *self, PyObject *unused) return PyLong_FromInt64(t); } -PyDoc_STRVAR(thread_time_ns_doc, -"thread_time() -> int\n\ -\n\ -Thread time for profiling as nanoseconds:\n\ -sum of the kernel and user-space CPU time."); - #ifdef __APPLE__ #pragma clang diagnostic pop #endif @@ -1637,18 +1691,23 @@ sum of the kernel and user-space CPU time."); #endif +/*[clinic input] +time.get_clock_info + + name: str + / + +Get information of the specified clock. +[clinic start generated code]*/ + static PyObject * -time_get_clock_info(PyObject *module, PyObject *args) +time_get_clock_info_impl(PyObject *module, const char *name) +/*[clinic end generated code: output=a77a07bdf3554bd6 input=6812ae049f1b1031]*/ { - char *name; _Py_clock_info_t info; PyObject *obj = NULL, *dict, *ns; PyTime_t t; - if (!PyArg_ParseTuple(args, "s:get_clock_info", &name)) { - return NULL; - } - #ifdef Py_DEBUG info.implementation = NULL; info.monotonic = -1; @@ -1760,11 +1819,6 @@ time_get_clock_info(PyObject *module, PyObject *args) return NULL; } -PyDoc_STRVAR(get_clock_info_doc, -"get_clock_info(name: str) -> dict\n\ -\n\ -Get information of the specified clock."); - #ifndef HAVE_DECL_TZNAME static void get_zone(char *zone, int n, struct tm *p) @@ -1913,48 +1967,48 @@ init_timezone(PyObject *m) #include "clinic/timemodule.c.h" static PyMethodDef time_methods[] = { - {"time", time_time, METH_NOARGS, time_doc}, - {"time_ns", time_time_ns, METH_NOARGS, time_ns_doc}, + TIME_TIME_METHODDEF + TIME_TIME_NS_METHODDEF #ifdef HAVE_CLOCK_GETTIME TIME_CLOCK_GETTIME_METHODDEF TIME_CLOCK_GETTIME_NS_METHODDEF #endif #ifdef HAVE_CLOCK_SETTIME - {"clock_settime", time_clock_settime, METH_VARARGS, clock_settime_doc}, - {"clock_settime_ns",time_clock_settime_ns, METH_VARARGS, clock_settime_ns_doc}, + TIME_CLOCK_SETTIME_METHODDEF + TIME_CLOCK_SETTIME_NS_METHODDEF #endif #ifdef HAVE_CLOCK_GETRES - {"clock_getres", time_clock_getres, METH_VARARGS, clock_getres_doc}, + TIME_CLOCK_GETRES_METHODDEF #endif #ifdef HAVE_PTHREAD_GETCPUCLOCKID - {"pthread_getcpuclockid", time_pthread_getcpuclockid, METH_VARARGS, pthread_getcpuclockid_doc}, + TIME_PTHREAD_GETCPUCLOCKID_METHODDEF #endif - {"sleep", time_sleep, METH_O, sleep_doc}, - {"gmtime", time_gmtime, METH_VARARGS, gmtime_doc}, - {"localtime", time_localtime, METH_VARARGS, localtime_doc}, - {"asctime", time_asctime, METH_VARARGS, asctime_doc}, - {"ctime", time_ctime, METH_VARARGS, ctime_doc}, + TIME_SLEEP_METHODDEF + TIME_GMTIME_METHODDEF + TIME_LOCALTIME_METHODDEF + TIME_ASCTIME_METHODDEF + TIME_CTIME_METHODDEF #ifdef HAVE_MKTIME - {"mktime", time_mktime, METH_O, mktime_doc}, + TIME_MKTIME_METHODDEF #endif #ifdef HAVE_STRFTIME {"strftime", time_strftime, METH_VARARGS, strftime_doc}, #endif {"strptime", time_strptime, METH_VARARGS, strptime_doc}, #ifdef HAVE_WORKING_TZSET - {"tzset", time_tzset, METH_NOARGS, tzset_doc}, + TIME_TZSET_METHODDEF #endif - {"monotonic", time_monotonic, METH_NOARGS, monotonic_doc}, - {"monotonic_ns", time_monotonic_ns, METH_NOARGS, monotonic_ns_doc}, - {"process_time", time_process_time, METH_NOARGS, process_time_doc}, - {"process_time_ns", time_process_time_ns, METH_NOARGS, process_time_ns_doc}, + TIME_MONOTONIC_METHODDEF + TIME_MONOTONIC_NS_METHODDEF + TIME_PROCESS_TIME_METHODDEF + TIME_PROCESS_TIME_NS_METHODDEF #ifdef HAVE_THREAD_TIME - {"thread_time", time_thread_time, METH_NOARGS, thread_time_doc}, - {"thread_time_ns", time_thread_time_ns, METH_NOARGS, thread_time_ns_doc}, + TIME_THREAD_TIME_METHODDEF + TIME_THREAD_TIME_NS_METHODDEF #endif - {"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc}, - {"perf_counter_ns", time_perf_counter_ns, METH_NOARGS, perf_counter_ns_doc}, - {"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc}, + TIME_PERF_COUNTER_METHODDEF + TIME_PERF_COUNTER_NS_METHODDEF + TIME_GET_CLOCK_INFO_METHODDEF {NULL, NULL} /* sentinel */ }; From b426caa04b1e165d4054125e67ee6c74141910a4 Mon Sep 17 00:00:00 2001 From: Gaurav Yadav Date: Mon, 21 Sep 2026 16:12:50 +0530 Subject: [PATCH 7/7] gh-157571: Move json sort note from dumps to dump (#157577) * gh-157571: Move json sort note from dumps to dump Signed-off-by: MeGaurav4 * "coerce" is not a Python term. Co-authored-by: Ned Batchelder --------- Signed-off-by: MeGaurav4 Co-authored-by: Ned Batchelder --- Doc/library/json.rst | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Doc/library/json.rst b/Doc/library/json.rst index ddd12a002f74163..0d115c5623fd7de 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -234,6 +234,16 @@ Basic Usage If ``True``, dictionaries will be outputted sorted by key. Default ``False``. + .. note:: + + Keys in key/value pairs of JSON are always of the type :class:`str`. When + a dictionary is converted into JSON, all the keys of the dictionary are + converted to strings. As a result of this, if a dictionary is converted + into JSON and then back into a dictionary, the dictionary may not equal + the original one. That is, ``loads(dumps(x)) != x`` if x has non-string + keys. *sort_keys* sorts the keys before they are converted to strings, + so numeric keys are sorted by value, not by their string representation. + .. versionchanged:: 3.2 Allow strings for *indent* in addition to integers. @@ -253,17 +263,6 @@ Basic Usage table `. The arguments have the same meaning as in :func:`dump`. - .. note:: - - Keys in key/value pairs of JSON are always of the type :class:`str`. When - a dictionary is converted into JSON, all the keys of the dictionary are - coerced to strings. As a result of this, if a dictionary is converted - into JSON and then back into a dictionary, the dictionary may not equal - the original one. That is, ``loads(dumps(x)) != x`` if x has non-string - keys. - *sort_keys* sorts the keys before they are coerced to strings, - so numeric keys are sorted by value, not by their string representation. - .. function:: load(fp, *, cls=None, object_hook=None, parse_float=None, \ parse_int=None, parse_constant=None, \ object_pairs_hook=None, array_hook=None, **kw)