[Python-checkins] gh-139353: Add Objects/unicode_format.c file (#139491)

2025-10-10 Thread vstinner
https://github.com/python/cpython/commit/4c119714d58912b10cb803a813da0364a0cc01e8
commit: 4c119714d58912b10cb803a813da0364a0cc01e8
branch: main
author: Victor Stinner 
committer: vstinner 
date: 2025-10-10T12:52:59+02:00
summary:

gh-139353: Add Objects/unicode_format.c file (#139491)

* Move PyUnicode_Format() implementation from unicodeobject.c
  to unicode_format.c.
* Replace unicode_modifiable() with _PyUnicode_IsModifiable()
* Add empty lines to have two empty lines between functions.

files:
A Objects/unicode_format.c
M Include/internal/pycore_unicodeobject.h
M Makefile.pre.in
M Objects/unicodeobject.c
M PCbuild/_freeze_module.vcxproj
M PCbuild/_freeze_module.vcxproj.filters
M PCbuild/pythoncore.vcxproj
M PCbuild/pythoncore.vcxproj.filters

diff --git a/Include/internal/pycore_unicodeobject.h 
b/Include/internal/pycore_unicodeobject.h
index f1c9bcd47888b1..b83039c1869f23 100644
--- a/Include/internal/pycore_unicodeobject.h
+++ b/Include/internal/pycore_unicodeobject.h
@@ -11,10 +11,14 @@ extern "C" {
 #include "pycore_fileutils.h" // _Py_error_handler
 #include "pycore_ucnhash.h"   // _PyUnicode_Name_CAPI
 
+
 // Maximum code point of Unicode 6.0: 0x10 (1,114,111).
 #define _Py_MAX_UNICODE 0x10
 
 
+extern int _PyUnicode_IsModifiable(PyObject *unicode);
+
+
 static inline void
 _PyUnicode_Fill(int kind, void *data, Py_UCS4 value,
 Py_ssize_t start, Py_ssize_t length)
@@ -48,6 +52,28 @@ _PyUnicode_Fill(int kind, void *data, Py_UCS4 value,
 }
 }
 
+static inline int
+_PyUnicode_EnsureUnicode(PyObject *obj)
+{
+if (!PyUnicode_Check(obj)) {
+PyErr_Format(PyExc_TypeError,
+ "must be str, not %T", obj);
+return -1;
+}
+return 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;
+PyUnicode_WRITE(writer->kind, writer->data, writer->pos, ch);
+writer->pos++;
+return 0;
+}
+
 
 /* --- Characters Type APIs --- */
 
diff --git a/Makefile.pre.in b/Makefile.pre.in
index a5223246845dcf..19423c11545c19 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -557,9 +557,10 @@ OBJECT_OBJS=   \
Objects/tupleobject.o \
Objects/typeobject.o \
Objects/typevarobject.o \
+   Objects/unicode_format.o \
Objects/unicode_formatter.o \
-   Objects/unicodeobject.o \
Objects/unicodectype.o \
+   Objects/unicodeobject.o \
Objects/unionobject.o \
Objects/weakrefobject.o \
@PERF_TRAMPOLINE_OBJ@
@@ -2105,6 +2106,7 @@ Objects/bytes_methods.o: 
$(srcdir)/Objects/bytes_methods.c $(BYTESTR_DEPS)
 Objects/bytesobject.o: $(srcdir)/Objects/bytesobject.c $(BYTESTR_DEPS)
 Objects/bytearrayobject.o: $(srcdir)/Objects/bytearrayobject.c $(BYTESTR_DEPS)
 
+Objects/unicode_format.o: $(srcdir)/Objects/unicode_format.c $(UNICODE_DEPS)
 Objects/unicodeobject.o: $(srcdir)/Objects/unicodeobject.c $(UNICODE_DEPS)
 
 Objects/dictobject.o: $(srcdir)/Objects/stringlib/eq.h
diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c
new file mode 100644
index 00..26bdae55d8b931
--- /dev/null
+++ b/Objects/unicode_format.c
@@ -0,0 +1,1002 @@
+/*
+
+Unicode implementation based on original code by Fredrik Lundh,
+modified by Marc-Andre Lemburg .
+
+Major speed upgrades to the method implementations at the Reykjavik
+NeedForSpeed sprint, by Fredrik Lundh and Andrew Dalke.
+
+Copyright (c) Corporation for National Research Initiatives.
+
+
+The original string type implementation is:
+
+  Copyright (c) 1999 by Secret Labs AB
+  Copyright (c) 1999 by Fredrik Lundh
+
+By obtaining, using, and/or copying this software and/or its
+associated documentation, you agree that you have read, understood,
+and will comply with the following terms and conditions:
+
+Permission to use, copy, modify, and distribute this software and its
+associated documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appears in all
+copies, and that both that copyright notice and this permission notice
+appear in supporting documentation, and that the name of Secret Labs
+AB or the author not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER T

[Python-checkins] gh-137025: Update Emscripten Build Docs (#137312)

2025-10-10 Thread freakboy3742
https://github.com/python/cpython/commit/34503111fe2724986701799dc994c6b0cb32f4f2
commit: 34503111fe2724986701799dc994c6b0cb32f4f2
branch: main
author: adam j hartz 
committer: freakboy3742 
date: 2025-10-10T06:36:40+08:00
summary:

gh-137025: Update Emscripten Build Docs (#137312)

Update Emscripten build docs to point at the devguide as the primary reference
for managing an Emscripten build.

files:
A Misc/NEWS.d/next/Tools-Demos/2025-08-01-13-27-43.gh-issue-137025.ubuhQC.rst
M Tools/wasm/README.md

diff --git 
a/Misc/NEWS.d/next/Tools-Demos/2025-08-01-13-27-43.gh-issue-137025.ubuhQC.rst 
b/Misc/NEWS.d/next/Tools-Demos/2025-08-01-13-27-43.gh-issue-137025.ubuhQC.rst
new file mode 100644
index 00..73c79564006526
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Tools-Demos/2025-08-01-13-27-43.gh-issue-137025.ubuhQC.rst
@@ -0,0 +1,3 @@
+The ``wasm_build.py`` script has been removed.  ``Tools/wasm/emscripten``
+and ``Tools/wasm/wasi`` should be used instead, as described in the `Dev
+Guide `__.
diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md
index efe9a3550c3e19..6615f1e2664196 100644
--- a/Tools/wasm/README.md
+++ b/Tools/wasm/README.md
@@ -9,68 +9,14 @@ compilation of CPython to WebAssembly (WASM). Python supports 
Emscripten
 run in modern browsers and JavaScript runtimes like *Node.js*. WASI builds
 use WASM runtimes such as *wasmtime*.
 
-Users and developers are encouraged to use the script
-`Tools/wasm/wasm_build.py`. The tool automates the build process and provides
-assistance with installation of SDKs, running tests, etc.
+**NOTE**: If you are looking for general information about WebAssembly that is
+not directly related to CPython, please see https://github.com/psf/webassembly.
 
-**NOTE**: If you are looking for information that is not directly related to
-building CPython for WebAssembly (or the resulting build), please see
-https://github.com/psf/webassembly for more information.
-
-## wasm32-emscripten
+## Emscripten (wasm32-emscripten)
 
 ### Build
 
-To cross compile to the ``wasm32-emscripten`` platform you need
-[the Emscripten compiler toolchain](https://emscripten.org/),
-a Python interpreter, and an installation of Node version 18 or newer.
-Emscripten version 4.0.2 is recommended; newer versions may also work, but all
-official testing is performed with that version. All commands below are 
relative
-to a checkout of the Python repository.
-
- Install [the Emscripten compiler 
toolchain](https://emscripten.org/docs/getting_started/downloads.html)
-
-You can install the Emscripten toolchain as follows:
-```shell
-git clone https://github.com/emscripten-core/emsdk.git --depth 1
-./emsdk/emsdk install latest
-./emsdk/emsdk activate latest
-```
-To add the Emscripten compiler to your path:
-```shell
-source ./emsdk/emsdk_env.sh
-```
-This adds `emcc` and `emconfigure` to your path.
-
-# Optionally: enable ccache for EMSDK
-
-The ``EM_COMPILER_WRAPPER`` must be set after the EMSDK environment is
-sourced. Otherwise the source script removes the environment variable.
-
-```shell
-export EM_COMPILER_WRAPPER=ccache
-```
-
- Compile and build Python interpreter
-
-You can use `python Tools/wasm/emscripten` to compile and build targeting
-Emscripten. You can do everything at once with:
-```shell
-python Tools/wasm/emscripten build
-```
-or you can break it out into four separate steps:
-```shell
-python Tools/wasm/emscripten configure-build-python
-python Tools/wasm/emscripten make-build-python
-python Tools/wasm/emscripten make-libffi
-python Tools/wasm/emscripten configure-host
-python Tools/wasm/emscripten make-host
-```
-Extra arguments to the configure steps are passed along to configure. For
-instance, to do a debug build, you can use:
-```shell
-python Tools/wasm/emscripten build --with-py-debug
-```
+See [the devguide instructions for building for 
Emscripten](https://devguide.python.org/getting-started/setup-building/#emscripten).
 
 ### Running from node
 
@@ -97,8 +43,8 @@ You can run the browser smoke test with:
 
 ### The Web Example
 
-When building for Emscripten, the web example will be built automatically. It
-is in the ``web_example`` directory. To run the web example, ``cd`` into the
+When building for Emscripten, a small web example will be built automatically
+in the ``web_example`` directory. To run the web example, ``cd`` into the
 ``web_example`` directory, then run ``python server.py``. This will start a web
 server; you can then visit ``http://localhost:8000/`` in a browser to see a
 simple REPL example.

___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]


[Python-checkins] gh-139894: fix incorrect sharing of current task while forking in `asyncio` (#139897)

2025-10-10 Thread kumaraditya303
https://github.com/python/cpython/commit/b881df47ff1adca515d1de04f689160ddae72142
commit: b881df47ff1adca515d1de04f689160ddae72142
branch: main
author: Kumar Aditya 
committer: kumaraditya303 
date: 2025-10-10T21:58:23+05:30
summary:

gh-139894: fix incorrect sharing of current task while forking in `asyncio`  
(#139897)

Fix incorrect sharing of current task with the forked child process by clearing 
thread state's current task and current loop in `PyOS_AfterFork_Child`.

files:
A Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst
M Lib/test/test_asyncio/test_unix_events.py
M Modules/posixmodule.c

diff --git a/Lib/test/test_asyncio/test_unix_events.py 
b/Lib/test/test_asyncio/test_unix_events.py
index a69a5e32b1b2bd..d2b3de3b9a4cb6 100644
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -1180,32 +1180,68 @@ async def runner():
 
 
 @support.requires_fork()
-class TestFork(unittest.IsolatedAsyncioTestCase):
+class TestFork(unittest.TestCase):
 
-async def test_fork_not_share_event_loop(self):
-with warnings_helper.ignore_fork_in_thread_deprecation_warnings():
-# The forked process should not share the event loop with the 
parent
-loop = asyncio.get_running_loop()
-r, w = os.pipe()
-self.addCleanup(os.close, r)
-self.addCleanup(os.close, w)
-pid = os.fork()
-if pid == 0:
-# child
-try:
-loop = asyncio.get_event_loop()
-os.write(w, b'LOOP:' + str(id(loop)).encode())
-except RuntimeError:
-os.write(w, b'NO LOOP')
-except BaseException as e:
-os.write(w, b'ERROR:' + ascii(e).encode())
-finally:
-os._exit(0)
-else:
-# parent
-result = os.read(r, 100)
-self.assertEqual(result, b'NO LOOP')
-wait_process(pid, exitcode=0)
+@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
+def test_fork_not_share_current_task(self):
+loop = object()
+task = object()
+asyncio._set_running_loop(loop)
+self.addCleanup(asyncio._set_running_loop, None)
+asyncio.tasks._enter_task(loop, task)
+self.addCleanup(asyncio.tasks._leave_task, loop, task)
+self.assertIs(asyncio.current_task(), task)
+r, w = os.pipe()
+self.addCleanup(os.close, r)
+self.addCleanup(os.close, w)
+pid = os.fork()
+if pid == 0:
+# child
+try:
+asyncio._set_running_loop(loop)
+current_task = asyncio.current_task()
+if current_task is None:
+os.write(w, b'NO TASK')
+else:
+os.write(w, b'TASK:' + str(id(current_task)).encode())
+except BaseException as e:
+os.write(w, b'ERROR:' + ascii(e).encode())
+finally:
+asyncio._set_running_loop(None)
+os._exit(0)
+else:
+# parent
+result = os.read(r, 100)
+self.assertEqual(result, b'NO TASK')
+wait_process(pid, exitcode=0)
+
+@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
+def test_fork_not_share_event_loop(self):
+# The forked process should not share the event loop with the parent
+loop = object()
+asyncio._set_running_loop(loop)
+self.assertIs(asyncio.get_running_loop(), loop)
+self.addCleanup(asyncio._set_running_loop, None)
+r, w = os.pipe()
+self.addCleanup(os.close, r)
+self.addCleanup(os.close, w)
+pid = os.fork()
+if pid == 0:
+# child
+try:
+loop = asyncio.get_event_loop()
+os.write(w, b'LOOP:' + str(id(loop)).encode())
+except RuntimeError:
+os.write(w, b'NO LOOP')
+except BaseException as e:
+os.write(w, b'ERROR:' + ascii(e).encode())
+finally:
+os._exit(0)
+else:
+# parent
+result = os.read(r, 100)
+self.assertEqual(result, b'NO LOOP')
+wait_process(pid, exitcode=0)
 
 @warnings_helper.ignore_fork_in_thread_deprecation_warnings()
 @hashlib_helper.requires_hashdigest('md5')
diff --git 
a/Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst 
b/Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst
new file mode 100644
index 00..05a977ad119e07
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst
@@ -0,0 +1 @@
+Fix incorrect sharing of current task with the child process while forking in 
:mod:`asyncio`. Patch by Kumar Aditya.
diff --git a/Modules/posixmo

[Python-checkins] gh-139783: Fix inspect.getsourcelines() for the case when a decorator is followed by a comment or an empty line (GH-139836)

2025-10-10 Thread serhiy-storchaka
https://github.com/python/cpython/commit/f4104f5d74b99712253fceb39a4460ee3f7a281c
commit: f4104f5d74b99712253fceb39a4460ee3f7a281c
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2025-10-10T10:51:24+03:00
summary:

gh-139783: Fix inspect.getsourcelines() for the case when a decorator is 
followed by a comment or an empty line (GH-139836)

files:
A Misc/NEWS.d/next/Library/2025-10-09-13-48-28.gh-issue-139783.__NUgo.rst
M Lib/inspect.py
M Lib/test/test_inspect/inspect_fodder2.py
M Lib/test/test_inspect/test_inspect.py

diff --git a/Lib/inspect.py b/Lib/inspect.py
index b345623b3fa2db..bb22bab3040fcb 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1065,7 +1065,9 @@ def __init__(self):
 
 def tokeneater(self, type, token, srowcol, erowcol, line):
 if not self.started and not self.indecorator:
-if type == tokenize.INDENT or token == "async":
+if type in (tokenize.INDENT, tokenize.COMMENT, tokenize.NL):
+pass
+elif token == "async":
 pass
 # skip any decorators
 elif token == "@":
diff --git a/Lib/test/test_inspect/inspect_fodder2.py 
b/Lib/test/test_inspect/inspect_fodder2.py
index 1de283f672d362..157e12167b5d27 100644
--- a/Lib/test/test_inspect/inspect_fodder2.py
+++ b/Lib/test/test_inspect/inspect_fodder2.py
@@ -388,4 +388,16 @@ def func383():
 )
 return ge385
 
+# line 391
+@decorator
+# comment
+def func394():
+return 395
+
+# line 397
+@decorator
+
+def func400():
+return 401
+
 pass # end of file
diff --git a/Lib/test/test_inspect/test_inspect.py 
b/Lib/test/test_inspect/test_inspect.py
index e32e34c63b5324..d42f2dbff99cae 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -1223,6 +1223,10 @@ def test_generator_expression(self):
 self.assertSourceEqual(next(mod2.ge377), 377, 380)
 self.assertSourceEqual(next(mod2.func383()), 385, 388)
 
+def test_comment_or_empty_line_after_decorator(self):
+self.assertSourceEqual(mod2.func394, 392, 395)
+self.assertSourceEqual(mod2.func400, 398, 401)
+
 
 class TestNoEOL(GetSourceBase):
 def setUp(self):
diff --git 
a/Misc/NEWS.d/next/Library/2025-10-09-13-48-28.gh-issue-139783.__NUgo.rst 
b/Misc/NEWS.d/next/Library/2025-10-09-13-48-28.gh-issue-139783.__NUgo.rst
new file mode 100644
index 00..336653e73bfa98
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-09-13-48-28.gh-issue-139783.__NUgo.rst
@@ -0,0 +1,2 @@
+Fix :func:`inspect.getsourcelines` for the case when a decorator is followed
+by a comment or an empty line.

___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]


[Python-checkins] gh-138843: Removing "Unpacking" section from Download page (GH-139918)

2025-10-10 Thread zware
https://github.com/python/cpython/commit/aa840f500ca901deea29669df68992193a273a62
commit: aa840f500ca901deea29669df68992193a273a62
branch: main
author: Stan Ulbrych <[email protected]>
committer: zware 
date: 2025-10-10T14:22:34-05:00
summary:

gh-138843: Removing "Unpacking" section from Download page (GH-139918)

files:
M Doc/tools/templates/download.html

diff --git a/Doc/tools/templates/download.html 
b/Doc/tools/templates/download.html
index 523b505f59673e..f914ad862116d7 100644
--- a/Doc/tools/templates/download.html
+++ b/Doc/tools/templates/download.html
@@ -75,16 +75,6 @@ {% trans %}Download Python {{ dl_version }} 
documentation{% endtrans %}
 See the https://docs.python.org/{{ version }}/archives/">directory 
listing
 for file sizes.{% endtrans %}
 
-{% trans %}Unpacking{% endtrans %}
-
-{% trans %}Unix users should download the .tar.bz2 archives; these are 
bzipped tar
-archives and can be handled in the usual way using tar and the bzip2
-program. The https://infozip.sourceforge.net";>Info-ZIP unzip 
program can be
-used to handle the ZIP archives if desired. The .tar.bz2 archives provide the
-best compression and fastest download times.{% endtrans %}
-
-{% trans %}Windows users can use the ZIP archives since those are customary 
on that
-platform. These are created on Unix using the Info-ZIP zip program.{% endtrans 
%}
 
 {% trans %}Problems{% endtrans %}
 {% set bugs = pathto('bugs') %}

___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]


[Python-checkins] gh-139769: Update `PCBuild/find_python.bat` to allow discovery of Python 3.14 (GH-139770)

2025-10-10 Thread zooba
https://github.com/python/cpython/commit/b6c14bc1874054909b233fde425fb3f6d2023e41
commit: b6c14bc1874054909b233fde425fb3f6d2023e41
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: zooba 
date: 2025-10-08T15:23:17Z
summary:

gh-139769: Update `PCBuild/find_python.bat` to allow discovery of Python 3.14 
(GH-139770)

Enable 3.14 py.exe can be use on PCBuild
(cherry picked from commit 570d17259f824302d20567a3a2f32c67ebdaefcd)

Co-authored-by: Wulian233 <[email protected]>

files:
M PCbuild/find_python.bat

diff --git a/PCbuild/find_python.bat b/PCbuild/find_python.bat
index d65d080ca71a90..841d83968c60be 100644
--- a/PCbuild/find_python.bat
+++ b/PCbuild/find_python.bat
@@ -47,7 +47,7 @@
 @rem If py.exe finds a recent enough version, use that one
 @rem It is fine to add new versions to this list when they have released,
 @rem but we do not use prerelease builds here.
-@for %%p in (3.13 3.12 3.11 3.10) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py 
-%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found
+@for %%p in (3.14 3.13 3.12 3.11 3.10) do @py -%%p -EV >nul 2>&1 && (set 
PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found
 
 @if NOT exist "%_Py_EXTERNALS_DIR%" mkdir "%_Py_EXTERNALS_DIR%"
 @set _Py_NUGET=%NUGET%

___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]


[Python-checkins] [3.14] gh-96491: Deduplicate version in IDLE shell title (GH-139841) (#139931)

2025-10-10 Thread terryjreedy
https://github.com/python/cpython/commit/1a355d73663692d77811686f5d655ce161a256a1
commit: 1a355d73663692d77811686f5d655ce161a256a1
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: terryjreedy 
date: 2025-10-11T02:02:34Z
summary:

[3.14] gh-96491: Deduplicate version in IDLE shell title (GH-139841) (#139931)

gh-96491: Deduplicate version in IDLE shell title (GH-139841)

Saving to a file added both the filename and repeated the version.
-
(cherry picked from commit d4e5802588db3459f04d4b8013cc571a8988e203)

Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Terry Jan Reedy 

files:
A Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst
M Lib/idlelib/CREDITS.txt
M Lib/idlelib/editor.py
M Lib/idlelib/idle_test/test_outwin.py
M Lib/idlelib/pyshell.py

diff --git a/Lib/idlelib/CREDITS.txt b/Lib/idlelib/CREDITS.txt
index bea3ba7c20de22..1b853e8cc1c462 100644
--- a/Lib/idlelib/CREDITS.txt
+++ b/Lib/idlelib/CREDITS.txt
@@ -37,6 +37,7 @@ Major contributors since 2005:
 - 2014: Saimadhav Heblikar
 - 2015: Mark Roseman
 - 2017: Louie Lu, Cheryl Sabella, and Serhiy Storchaka
+- 2025: Stan Ulbrych
 
 For additional details refer to NEWS.txt and Changelog.
 
diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
index b4d6d25871bcf4..83112d85575e47 100644
--- a/Lib/idlelib/editor.py
+++ b/Lib/idlelib/editor.py
@@ -33,7 +33,6 @@
 
 # The default tab setting for a Text widget, in average-width characters.
 TK_TABWIDTH_DEFAULT = 8
-_py_version = ' (%s)' % platform.python_version()
 darwin = sys.platform == 'darwin'
 
 def _sphinx_version():
@@ -1008,12 +1007,16 @@ def open_recent_file(fn_closure=file_name):
 def saved_change_hook(self):
 short = self.short_title()
 long = self.long_title()
+_py_version = ' (%s)' % platform.python_version()
 if short and long and not macosx.isCocoaTk():
 # Don't use both values on macOS because
 # that doesn't match platform conventions.
 title = short + " - " + long + _py_version
 elif short:
-title = short
+if short == "IDLE Shell":
+title = short + " " +  platform.python_version()
+else:
+title = short + _py_version
 elif long:
 title = long
 else:
diff --git a/Lib/idlelib/idle_test/test_outwin.py 
b/Lib/idlelib/idle_test/test_outwin.py
index 81f4aad7e95e95..0f13363f84f361 100644
--- a/Lib/idlelib/idle_test/test_outwin.py
+++ b/Lib/idlelib/idle_test/test_outwin.py
@@ -1,6 +1,7 @@
 "Test outwin, coverage 76%."
 
 from idlelib import outwin
+import platform
 import sys
 import unittest
 from test.support import requires
@@ -41,7 +42,7 @@ def test_ispythonsource(self):
 self.assertFalse(w.ispythonsource(__file__))
 
 def test_window_title(self):
-self.assertEqual(self.window.top.title(), 'Output')
+self.assertEqual(self.window.top.title(), 'Output' + ' (%s)' % 
platform.python_version())
 
 def test_maybesave(self):
 w = self.window
diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py
index 74a0e03994f69a..1b7c2af1a923d7 100755
--- a/Lib/idlelib/pyshell.py
+++ b/Lib/idlelib/pyshell.py
@@ -22,7 +22,6 @@
 import linecache
 import os
 import os.path
-from platform import python_version
 import re
 import socket
 import subprocess
@@ -841,7 +840,7 @@ def display_executing_dialog(self):
 class PyShell(OutputWindow):
 from idlelib.squeezer import Squeezer
 
-shell_title = "IDLE Shell " + python_version()
+shell_title = "IDLE Shell"
 
 # Override classes
 ColorDelegator = ModifiedColorDelegator
diff --git 
a/Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst 
b/Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst
new file mode 100644
index 00..beb6ef5ade562f
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst
@@ -0,0 +1 @@
+Deduplicate version number in IDLE shell title bar after saving to a file.

___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]