diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index 18b89a20e4a208..dbd73313b4f777 100644 --- a/Doc/deprecations/pending-removal-in-3.21.rst +++ b/Doc/deprecations/pending-removal-in-3.21.rst @@ -17,3 +17,9 @@ Pending removal in Python 3.21 are not generated by the parser or accepted by the code generator. * The ``dims`` property of ``ast.Tuple`` will be removed in Python 3.21. Use the ``ast.Tuple.elts`` property instead. + +* :mod:`struct`: + + * Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now + deprecated. These codes will be removed in Python 3.21. Use instead + two-letter forms ``'Zf'`` and ``'Zd'``. diff --git a/Doc/deprecations/soft-deprecations.rst b/Doc/deprecations/soft-deprecations.rst index d1dd2b2d8c520d..a270052788ef2a 100644 --- a/Doc/deprecations/soft-deprecations.rst +++ b/Doc/deprecations/soft-deprecations.rst @@ -19,8 +19,3 @@ There are no plans to remove :term:`soft deprecated` APIs. (Contributed by Gregory P. Smith in :gh:`86519` and Hugo van Kemenade in :gh:`148100`.) - -* Using ``'F'`` and ``'D'`` format type codes of the :mod:`struct` module - now are :term:`soft deprecated` in favor of two-letter forms ``'Zf'`` - and ``'Zd'``. - (Contributed by Sergey B Kirpichev in :gh:`121249`.) diff --git a/Doc/faq/design.rst b/Doc/faq/design.rst index ac0aa81e56bb07..c914089e9806ec 100644 --- a/Doc/faq/design.rst +++ b/Doc/faq/design.rst @@ -263,6 +263,8 @@ In general, structured switch statements execute one block of code when an expression has a particular value or set of values. Since Python 3.10 one can easily match literal values, or constants within a namespace, with a ``match ... case`` statement. +See :ref:`the specification ` and :ref:`the tutorial ` +for more information about :keyword:`match` statements. An older alternative is a sequence of ``if... elif... elif... else``. For cases where you need to choose from a very large number of possibilities, diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index b3341fb8629f4c..7178325e708bc1 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -465,6 +465,8 @@ Mouse Return ``True`` if the mouse driver has been successfully initialized. + Availability: ncurses 5.8 or later. + .. versionadded:: next diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index 775e5b2074851b..ed5a719028d60e 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -283,7 +283,9 @@ platform-dependent. .. versionchanged:: 3.15 Added support for the ``'Zf'`` and ``'Zd'`` formats. - ``'F'`` and ``'D'`` formats are :term:`soft deprecated`. + +.. versionchanged:: 3.16 + ``'F'`` and ``'D'`` formats are deprecated. .. seealso:: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index e215d4ddfdf41b..1a73a79a58b78b 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -550,6 +550,13 @@ New deprecations 3.9, now issues a deprecation warning on use. This property is slated for removal in 3.21. Use ``ast.Tuple.elts`` instead. +* :mod:`struct`: + + * Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now + deprecated. These codes will be removed in Python 3.21. Use instead + two-letter forms ``'Zf'`` and ``'Zd'``. + (Contributed by Sergey B Kirpichev in :gh:`121249`.) + .. Add deprecations above alphabetically, not here at the end. .. include:: ../deprecations/pending-removal-in-3.17.rst diff --git a/Lib/idlelib/idle_test/template.py b/Lib/idlelib/idle_test/template.py index 6e98556458ec58..0a4bd8b8e981fc 100644 --- a/Lib/idlelib/idle_test/template.py +++ b/Lib/idlelib/idle_test/template.py @@ -16,7 +16,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.root.update_idletasks() -## for id in cls.root.tk.call('after', 'info'): +## for id in cls.root.after_info(): ## cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_codecontext.py b/Lib/idlelib/idle_test/test_codecontext.py index 6969ad73b01a81..3f070da022384e 100644 --- a/Lib/idlelib/idle_test/test_codecontext.py +++ b/Lib/idlelib/idle_test/test_codecontext.py @@ -127,7 +127,7 @@ def test_del_with_timer(self): timer = self.cc.t1 = self.text.after(10000, lambda: None) self.cc.__del__() with self.assertRaises(TclError) as cm: - self.root.tk.call('after', 'info', timer) + self.root.after_info(timer) self.assertIn("doesn't exist", str(cm.exception)) def test_reload(self): @@ -151,7 +151,7 @@ def test_toggle_code_context_event(self): eq(cc.context['bg'], self.highlight_cfg['background']) eq(cc.context.get('1.0', 'end-1c'), '') eq(cc.editwin.label, 'Hide Code Context') - eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer') + eq(self.root.after_info(self.cc.t1)[1], 'timer') # Toggle off. toggle() diff --git a/Lib/idlelib/idle_test/test_colorizer.py b/Lib/idlelib/idle_test/test_colorizer.py index fb6ee825086750..4d9e6bd887c5b8 100644 --- a/Lib/idlelib/idle_test/test_colorizer.py +++ b/Lib/idlelib/idle_test/test_colorizer.py @@ -260,7 +260,7 @@ def test_notify_range(self): # Colorizing already scheduled. save_id = color.after_id - eq(self.root.tk.call('after', 'info', save_id)[1], 'timer') + eq(self.root.after_info(save_id)[1], 'timer') self.assertFalse(color.colorizing) self.assertFalse(color.stop_colorizing) self.assertTrue(color.allow_colorizing) @@ -277,7 +277,7 @@ def test_notify_range(self): color.notify_range('1.0', '1.0+3c') self.assertTrue(color.stop_colorizing) self.assertIsNotNone(color.after_id) - eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer') + eq(self.root.after_info(color.after_id)[1], 'timer') # New event scheduled. self.assertNotEqual(color.after_id, save_id) @@ -297,7 +297,7 @@ def test_toggle_colorize_event(self): self.assertFalse(color.colorizing) self.assertFalse(color.stop_colorizing) self.assertTrue(color.allow_colorizing) - eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer') + eq(self.root.after_info(color.after_id)[1], 'timer') # Toggle colorizing off. color.toggle_colorize_event() @@ -324,7 +324,7 @@ def test_toggle_colorize_event(self): # Toggle on while colorizing not in progress. color.colorizing = False color.toggle_colorize_event() - eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer') + eq(self.root.after_info(color.after_id)[1], 'timer') self.assertFalse(color.colorizing) self.assertTrue(color.stop_colorizing) self.assertTrue(color.allow_colorizing) @@ -363,7 +363,7 @@ def test_recolorize(self, mock_recmain): mock_recmain.assert_called() eq(mock_recmain.call_count, 1) # Rescheduled when TODO tag still exists. - eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer') + eq(self.root.after_info(color.after_id)[1], 'timer') # No changes to text, so no scheduling added. text.tag_remove('TODO', '1.0', 'end') diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 0dfe2f3c58befa..e28ee549f180aa 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -20,7 +20,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) cls.root.destroy() del cls.root @@ -114,7 +114,7 @@ def tearDownClass(cls): cls.window._close() del cls.window cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) cls.root.destroy() del cls.root @@ -225,7 +225,7 @@ def tearDownClass(cls): cls.window._close() del cls.window cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_filelist.py b/Lib/idlelib/idle_test/test_filelist.py index 731f1975e50e23..e22cc3eced3082 100644 --- a/Lib/idlelib/idle_test/test_filelist.py +++ b/Lib/idlelib/idle_test/test_filelist.py @@ -16,7 +16,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 80f72bdfe5ff0e..90a23620354f31 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -30,7 +30,7 @@ def tearDownClass(cls): cls.editwin._close() del cls.editwin cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_multicall.py b/Lib/idlelib/idle_test/test_multicall.py index 67f28db6b0875c..7d73761cfdfee8 100644 --- a/Lib/idlelib/idle_test/test_multicall.py +++ b/Lib/idlelib/idle_test/test_multicall.py @@ -19,7 +19,7 @@ def setUpClass(cls): def tearDownClass(cls): del cls.mc cls.root.update_idletasks() -## for id in cls.root.tk.call('after', 'info'): +## for id in cls.root.after_info(): ## cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_pyshell.py b/Lib/idlelib/idle_test/test_pyshell.py index 706703965bffd6..51f7691eefe9d5 100644 --- a/Lib/idlelib/idle_test/test_pyshell.py +++ b/Lib/idlelib/idle_test/test_pyshell.py @@ -40,7 +40,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): #cls.root.update_idletasks() -## for id in cls.root.tk.call('after', 'info'): +## for id in cls.root.after_info(): ## cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_runscript.py b/Lib/idlelib/idle_test/test_runscript.py index 5fc60185a663e8..63086bfa4a404e 100644 --- a/Lib/idlelib/idle_test/test_runscript.py +++ b/Lib/idlelib/idle_test/test_runscript.py @@ -18,7 +18,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_stackviewer.py b/Lib/idlelib/idle_test/test_stackviewer.py index 55f510382bf4c3..2434d38e4ffe83 100644 --- a/Lib/idlelib/idle_test/test_stackviewer.py +++ b/Lib/idlelib/idle_test/test_stackviewer.py @@ -21,7 +21,7 @@ def setUpClass(cls): def tearDownClass(cls): cls.root.update_idletasks() -## for id in cls.root.tk.call('after', 'info'): +## for id in cls.root.after_info(): ## cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_window.py b/Lib/idlelib/idle_test/test_window.py index 5a2645b9cc27dc..9b56d321a407d6 100644 --- a/Lib/idlelib/idle_test/test_window.py +++ b/Lib/idlelib/idle_test/test_window.py @@ -29,7 +29,7 @@ def setUpClass(cls): def tearDownClass(cls): window.registry = window.WindowList() cls.root.update_idletasks() -## for id in cls.root.tk.call('after', 'info'): +## for id in cls.root.after_info(): ## cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_zoomheight.py b/Lib/idlelib/idle_test/test_zoomheight.py index aa5bdfb4fbd4c6..3b97c34d4ab29d 100644 --- a/Lib/idlelib/idle_test/test_zoomheight.py +++ b/Lib/idlelib/idle_test/test_zoomheight.py @@ -21,7 +21,7 @@ def setUpClass(cls): def tearDownClass(cls): cls.editwin._close() cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_zzdummy.py b/Lib/idlelib/idle_test/test_zzdummy.py index 14c343cf9b3087..c8e7dd6941b589 100644 --- a/Lib/idlelib/idle_test/test_zzdummy.py +++ b/Lib/idlelib/idle_test/test_zzdummy.py @@ -135,7 +135,7 @@ def tearDownClass(cls): zzdummy.idleConf.userCfg = usercfg del cls.editor, cls.text cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_zzdummy_user.py b/Lib/idlelib/idle_test/test_zzdummy_user.py index 1d3f2ac3096fb0..a3476bf52bc588 100644 --- a/Lib/idlelib/idle_test/test_zzdummy_user.py +++ b/Lib/idlelib/idle_test/test_zzdummy_user.py @@ -68,7 +68,7 @@ def tearDownClass(cls): zzdummy.idleConf.userCfg = real_usercfg del cls.editor, cls.text cls.root.update_idletasks() - for id in cls.root.tk.call('after', 'info'): + for id in cls.root.after_info(): cls.root.after_cancel(id) # Need for EditorWindow. cls.root.destroy() del cls.root diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index 332952f4572cbd..428e49f3eb7d8e 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -39,7 +39,7 @@ def _init_tk_type(): _tk_type = "xquartz" elif 'aqua' not in ws: _tk_type = "other" - elif 'AppKit' in root.tk.call('winfo', 'server', '.'): + elif 'AppKit' in root.winfo_server(): _tk_type = "cocoa" else: _tk_type = "carbon" diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2248920c266aef..25b2c393e6773d 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1,4 +1,5 @@ import contextlib +import dis import itertools import sys import textwrap @@ -247,6 +248,28 @@ def many_vars(): self.assertTrue(any((opcode, oparg, operand) == ("_LOAD_FAST_BORROW", 259, 0) for opcode, oparg, _, operand in list(ex))) + def test_jump_backward_extended_arg(self): + # gh-152192: a JUMP_BACKWARD that needs an EXTENDED_ARG must record its + # deopt target at the EXTENDED_ARG, not the JUMP_BACKWARD. + ns = {} + src = ("def f(n):\n" + " i = 0\n" + " while i < n:\n" + " i += 1\n" + + "".join(f" a = {j}\n" for j in range(140))) + exec(src, ns) + f = ns["f"] + + instrs = list(dis.get_instructions(f)) + ext, jb = next((p, i) for p, i in zip(instrs, instrs[1:]) + if i.opname == "JUMP_BACKWARD" and p.opname == "EXTENDED_ARG") + + f(TIER2_THRESHOLD + 1) + ex = _opcode.get_executor(f.__code__, ext.offset) + set_ips = {t for op, _, t, _ in ex if op == "_SET_IP"} + self.assertIn(ext.offset // 2, set_ips) + self.assertNotIn(jb.offset // 2, set_ips) + def test_unspecialized_unpack(self): # An example of an unspecialized opcode def testfunc(x): diff --git a/Lib/test/test_ctypes/test_byteswap.py b/Lib/test/test_ctypes/test_byteswap.py index 6a1bae14773d27..1351b3e6bd2fa4 100644 --- a/Lib/test/test_ctypes/test_byteswap.py +++ b/Lib/test/test_ctypes/test_byteswap.py @@ -178,14 +178,14 @@ def test_endian_float_complex(self): self.assertIs(c_float_complex.__ctype_le__.__ctype_be__, c_float_complex) s = c_float_complex(math.pi+1j) - self.assertEqual(bin(struct.pack("F", math.pi+1j)), bin(s)) + self.assertEqual(bin(struct.pack("Zf", math.pi+1j)), bin(s)) self.assertAlmostEqual(s.value, math.pi+1j, places=6) s = c_float_complex.__ctype_le__(math.pi+1j) self.assertAlmostEqual(s.value, math.pi+1j, places=6) - self.assertEqual(bin(struct.pack("F", math.pi+1j)), bin(s)) + self.assertEqual(bin(struct.pack(">Zf", math.pi+1j)), bin(s)) @unittest.skipUnless(hasattr(ctypes, 'c_double_complex'), "No complex types") def test_endian_double_complex(self): @@ -199,14 +199,14 @@ def test_endian_double_complex(self): self.assertIs(c_double_complex.__ctype_le__.__ctype_be__, c_double_complex) s = c_double_complex(math.pi+1j) - self.assertEqual(bin(struct.pack("D", math.pi+1j)), bin(s)) + self.assertEqual(bin(struct.pack("Zd", math.pi+1j)), bin(s)) self.assertAlmostEqual(s.value, math.pi+1j, places=6) s = c_double_complex.__ctype_le__(math.pi+1j) self.assertAlmostEqual(s.value, math.pi+1j, places=6) - self.assertEqual(bin(struct.pack("D", math.pi+1j)), bin(s)) + self.assertEqual(bin(struct.pack(">Zd", math.pi+1j)), bin(s)) def test_endian_other(self): self.assertIs(c_byte.__ctype_le__, c_byte) diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index f71b6f53486509..df5ab9f2c879ca 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -717,8 +717,8 @@ def test_half_float(self): self.assertListEqual(half_view.tolist(), float_view.tolist()) def test_complex_types(self): - float_complex_data = struct.pack('FFF', 0.0, -1.5j, 1+2j) - double_complex_data = struct.pack('DDD', 0.0, -1.5j, 1+2j) + float_complex_data = struct.pack('ZfZfZf', 0.0, -1.5j, 1+2j) + double_complex_data = struct.pack('ZdZdZd', 0.0, -1.5j, 1+2j) float_complex_view = memoryview(float_complex_data).cast('Zf') double_complex_view = memoryview(double_complex_data).cast('Zd') self.assertEqual(float_complex_view.nbytes * 2, double_complex_view.nbytes) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index b8861d09e1564b..5c2d69934b02ea 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -14,9 +14,6 @@ import test.support from test.support import import_helper, requires_specialization, script_helper -_testcapi = import_helper.import_module("_testcapi") -_testinternalcapi = import_helper.import_module("_testinternalcapi") - PAIR = (0,1) def f1(): @@ -867,6 +864,8 @@ def test_implicit_stop_iteration(self): This test checks that both paths record an equivalent event. """ + _testinternalcapi = import_helper.import_module("_testinternalcapi") + def gen(): yield 1 return 2 @@ -1034,6 +1033,8 @@ def func(): @requires_specialization def test_no_unwind_for_shim_frame(self): + _testinternalcapi = import_helper.import_module("_testinternalcapi") + class ValueErrorRaiser: def __init__(self): raise ValueError() @@ -2455,6 +2456,7 @@ def run(): sys.monitoring.set_events(TEST_TOOL, 0) def test_108390(self): + _testinternalcapi = import_helper.import_module("_testinternalcapi") class Foo: def __init__(self, set_event): @@ -2546,6 +2548,8 @@ def test_func(x): class TestTier2Optimizer(CheckEvents): def test_monitoring_already_opimized_loop(self): + _testinternalcapi = import_helper.import_module("_testinternalcapi") + def test_func(recorder): set_events = sys.monitoring.set_events line = E.LINE @@ -2582,18 +2586,20 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase): class Scope: def __init__(self, *args): + self._testcapi = import_helper.import_module("_testcapi") self.args = args def __enter__(self): - _testcapi.monitoring_enter_scope(*self.args) + self._testcapi.monitoring_enter_scope(*self.args) def __exit__(self, *args): - _testcapi.monitoring_exit_scope() + self._testcapi.monitoring_exit_scope() def setUp(self): super(TestCApiEventGeneration, self).setUp() - capi = _testcapi + self._testcapi = import_helper.import_module("_testcapi") + capi = self._testcapi self.codelike = capi.CodeLike(2) @@ -2662,7 +2668,7 @@ def __call__(self, *args): def test_fire_event(self): for expected, event, function, *args in self.cases: offset = 0 - self.codelike = _testcapi.CodeLike(1) + self.codelike = self._testcapi.CodeLike(1) with self.subTest(function.__name__): args_ = (self.codelike, offset) + tuple(args) self.check_event_count(event, function, args_, expected) @@ -2673,7 +2679,7 @@ def test_missing_exception(self): continue assert args and isinstance(args[-1], BaseException) offset = 0 - self.codelike = _testcapi.CodeLike(1) + self.codelike = self._testcapi.CodeLike(1) with self.subTest(function.__name__): args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,) evt = int(math.log2(event)) @@ -2683,7 +2689,7 @@ def test_missing_exception(self): def test_fire_event_failing_callback(self): for expected, event, function, *args in self.cases: offset = 0 - self.codelike = _testcapi.CodeLike(1) + self.codelike = self._testcapi.CodeLike(1) with self.subTest(function.__name__): args_ = (self.codelike, offset) + tuple(args) exc = OSError(42) @@ -2733,12 +2739,14 @@ def check_disable(self, event, func, args, expected): def test_disable_event(self): for expected, event, function, *args in self.cases: offset = 0 - self.codelike = _testcapi.CodeLike(2) + self.codelike = self._testcapi.CodeLike(2) with self.subTest(function.__name__): args_ = (self.codelike, 0) + tuple(args) self.check_disable(event, function, args_, expected) def test_enter_scope_two_events(self): + _testcapi = self._testcapi + try: yield_counter = CounterWithDisable() unwind_counter = CounterWithDisable() diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index edd85df633fc3b..f9803abb3db91f 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -7,6 +7,7 @@ import unittest import struct import sys +import warnings import weakref from test import support @@ -995,14 +996,26 @@ def test_c_complex_round_trip(self): values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] for z in values: - for f in [ - 'F', 'D', 'Zf', 'Zd', - '>F', '>D', '>Zf', '>Zd', - 'Zf', '>Zd', 'F', '>D', '5", + "AAA4<>,M3.2.0/2,M11.1.0/3", "PST8PDT,M3.2.0/2", # Only one transition rule # Invalid offset hours "AAA168", @@ -1232,6 +1239,15 @@ def test_invalid_tzstr(self): with self.assertRaisesRegex(ValueError, tzstr_regex): self.zone_from_tzstr(invalid_tzstr) + def test_invalid_tzstr_non_ascii_abbr(self): + tzstr = "ABÀC3" + if self.module is py_zoneinfo: + expected = re.escape(tzstr) + else: + expected = re.escape(repr(tzstr.encode("utf-8"))) + with self.assertRaisesRegex(ValueError, expected): + self.zone_from_tzstr(tzstr, encoding="utf-8") + @classmethod def _populate_test_cases(cls): # This method uses a somewhat unusual style in that it populates the diff --git a/Lib/turtle.py b/Lib/turtle.py index b52d681b3af1c3..af2c7448a0af17 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -486,6 +486,7 @@ def __init__(self, cv): self.canvwidth = w self.canvheight = h self.xscale = self.yscale = 1.0 + self._updating = False def _createpoly(self): """Create an invisible polygon item on canvas self.cv) @@ -555,7 +556,16 @@ def _delete(self, item): def _update(self): """Redraw graphics items on canvas """ - self.cv.update() + if self._updating: + # Reentrant call (e.g. a drag handler moving the turtle, + # gh-50966): flush drawing without reprocessing input. + self.cv.update_idletasks() + return + self._updating = True + try: + self.cv.update() + finally: + self._updating = False def _delay(self, delay): """Delay subsequent canvas actions for delay ms.""" @@ -989,8 +999,8 @@ def __init__(self, cv, mode=_CFG["mode"], # the Turtle window will show behind the Terminal window when you # start the demo from the command line. rootwindow = cv.winfo_toplevel() - rootwindow.call('wm', 'attributes', '.', '-topmost', '1') - rootwindow.call('wm', 'attributes', '.', '-topmost', '0') + rootwindow.wm_attributes(topmost=True) + rootwindow.wm_attributes(topmost=False) def clear(self): """Delete all drawings and all turtles from the TurtleScreen. diff --git a/Lib/zoneinfo/_zoneinfo.py b/Lib/zoneinfo/_zoneinfo.py index 52832f600c3044..90cf2bbf8f5d0d 100644 --- a/Lib/zoneinfo/_zoneinfo.py +++ b/Lib/zoneinfo/_zoneinfo.py @@ -640,11 +640,11 @@ def _parse_tz_str(tz_str): parser_re = re.compile( r""" - (?P[^<0-9:.+-]+|<[a-zA-Z0-9+-]+>) + (?P[a-zA-Z]+|<[a-zA-Z0-9+-]+>) (?: (?P[+-]?\d{1,3}(?::\d{2}(?::\d{2})?)?) (?: - (?P[^0-9:.+-]+|<[a-zA-Z0-9+-]+>) + (?P[a-zA-Z]+|<[a-zA-Z0-9+-]+>) (?P[+-]?\d{1,3}(?::\d{2}(?::\d{2})?)?)? )? # dst )? # stdoff diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst new file mode 100644 index 00000000000000..153cbeb317a245 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-38-21.gh-issue-152192.lX2jIM.rst @@ -0,0 +1,2 @@ +Fix a truncated ``oparg`` being passed to JIT trace initialization for a +``JUMP_BACKWARD`` with an ``EXTENDED_ARG``. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-29-23-29-14.gh-issue-152635.O21J0O.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-29-23-29-14.gh-issue-152635.O21J0O.rst new file mode 100644 index 00000000000000..dadb25fc7c4477 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-29-23-29-14.gh-issue-152635.O21J0O.rst @@ -0,0 +1,2 @@ +Fix a crash caused when running out of memory creating a +:mod:`!_interpchannels` channel. Now a :exc:`MemoryError` is correctly raised. diff --git a/Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst b/Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst new file mode 100644 index 00000000000000..8921ad17349e97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst @@ -0,0 +1,3 @@ +Make the C and pure-Python :mod:`zoneinfo` parsers validate POSIX TZ +abbreviations consistently, rejecting unquoted abbreviations with non-letter +characters and empty quoted abbreviations. Patch by tonghuaroot. diff --git a/Misc/NEWS.d/next/Library/2026-06-26-19-01-13.gh-issue-121249.p1SBW0.rst b/Misc/NEWS.d/next/Library/2026-06-26-19-01-13.gh-issue-121249.p1SBW0.rst new file mode 100644 index 00000000000000..0e676e7774d1d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-19-01-13.gh-issue-121249.p1SBW0.rst @@ -0,0 +1 @@ +Deprecate using ``'F'`` and ``'D'`` type codes in the :mod:`struct` module. diff --git a/Misc/NEWS.d/next/Library/2026-06-29-22-16-57.gh-issue-50966.Tq5mLp.rst b/Misc/NEWS.d/next/Library/2026-06-29-22-16-57.gh-issue-50966.Tq5mLp.rst new file mode 100644 index 00000000000000..96a0938c02d226 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-29-22-16-57.gh-issue-50966.Tq5mLp.rst @@ -0,0 +1,3 @@ +Fix unbounded recursion in :mod:`turtle` when a mouse event handler that moves +the turtle is reentered while the screen is being redrawn, for example with +``screen.ondrag(turtle.goto)``. This could previously crash the interpreter. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index cbd45216968470..dc373c8cef932c 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -7041,6 +7041,8 @@ _curses_meta_impl(PyObject *module, int yes) } #ifdef NCURSES_MOUSE_VERSION +/* has_mouse() was added to ncurses after the 5.7 release. */ +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081122 /*[clinic input] _curses.has_mouse @@ -7055,6 +7057,7 @@ _curses_has_mouse_impl(PyObject *module) return PyBool_FromLong(has_mouse()); } +#endif /* NCURSES_EXT_FUNCS >= 20081122 */ /*[clinic input] _curses.mouseinterval diff --git a/Modules/_interpchannelsmodule.c b/Modules/_interpchannelsmodule.c index 05957081079d4a..358d51cf13f1af 100644 --- a/Modules/_interpchannelsmodule.c +++ b/Modules/_interpchannelsmodule.c @@ -354,7 +354,7 @@ clear_module_state(module_state *state) #define ERR_CHANNEL_INTERP_CLOSED -4 #define ERR_CHANNEL_EMPTY -5 #define ERR_CHANNEL_NOT_EMPTY -6 -#define ERR_CHANNEL_MUTEX_INIT -7 +#define ERR_CHANNEL_MUTEX_INIT -7 // currently unused #define ERR_CHANNELS_MUTEX_INIT -8 #define ERR_NO_NEXT_CHANNEL_ID -9 #define ERR_CHANNEL_CLOSED_WAITING -10 @@ -427,10 +427,6 @@ handle_channel_error(int err, PyObject *mod, int64_t cid) "if not empty (try force=True)", cid); } - else if (err == ERR_CHANNEL_MUTEX_INIT) { - PyErr_SetString(state->ChannelError, - "can't initialize mutex for new channel"); - } else if (err == ERR_CHANNELS_MUTEX_INIT) { PyErr_SetString(state->ChannelError, "can't initialize mutex for channel management"); @@ -1744,7 +1740,8 @@ channel_create(_channels *channels, struct _channeldefaults defaults) { PyThread_type_lock mutex = PyThread_allocate_lock(); if (mutex == NULL) { - return ERR_CHANNEL_MUTEX_INIT; + PyErr_NoMemory(); + return -1; } _channel_state *chan = _channel_new(mutex, defaults); if (chan == NULL) { @@ -2938,7 +2935,7 @@ channelsmod_create(PyObject *self, PyObject *args, PyObject *kwds) int64_t cid = channel_create(&_globals.channels, defaults); if (cid < 0) { - (void)handle_channel_error(-1, self, cid); + (void)handle_channel_error(cid, self, cid); return NULL; } module_state *state = get_module_state(self); diff --git a/Modules/_struct.c b/Modules/_struct.c index 8c611a708d02a9..6bf3993439cf1d 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1697,6 +1697,21 @@ prepare_s(PyStructObject *self, PyObject *format) if (e == NULL) return -1; + if (strcmp(e->format, "F") == 0) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "The 'F' type code is deprecated, use 'Zf'", 1)) + { + return -1; + } + } + if (strcmp(e->format, "D") == 0) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "The 'D' type code is deprecated, use 'Zd'", 1)) + { + return -1; + } + } + switch (c) { case 's': _Py_FALLTHROUGH; case 'p': @@ -2065,6 +2080,20 @@ s_unpack_internal(PyStructObject *soself, const char *startfrom, } v = PyBytes_FromStringAndSize(res + 1, n); } else { + if (strcmp(e->format, "F") == 0) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "The 'F' type code is deprecated, use 'Zf'", 1)) + { + goto fail; + } + } + if (strcmp(e->format, "D") == 0) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "The 'D' type code is deprecated, use 'Zd'", 1)) + { + goto fail; + } + } v = e->unpack(state, res, e); } if (v == NULL) diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index fdc077c9549a14..51234e8749de26 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -8582,8 +8582,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, @@ -11575,8 +11574,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index eaffd020ed97c0..2a7ac4498261e0 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -1762,6 +1762,9 @@ parse_abbr(const char **p, PyObject **abbr) ptr++; } str_end = ptr; + if (str_end == str_start) { + return -1; + } ptr++; } else { diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index 78619842f97fd7..e69be47549cf65 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -4232,7 +4232,7 @@ _curses_meta(PyObject *module, PyObject *arg) return return_value; } -#if defined(NCURSES_MOUSE_VERSION) +#if defined(NCURSES_MOUSE_VERSION) && (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081122) PyDoc_STRVAR(_curses_has_mouse__doc__, "has_mouse($module, /)\n" @@ -4252,7 +4252,7 @@ _curses_has_mouse(PyObject *module, PyObject *Py_UNUSED(ignored)) return _curses_has_mouse_impl(module); } -#endif /* defined(NCURSES_MOUSE_VERSION) */ +#endif /* defined(NCURSES_MOUSE_VERSION) && (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081122) */ #if defined(NCURSES_MOUSE_VERSION) @@ -6174,4 +6174,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=55829d2ef2b559a0 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=809e4680d429b870 input=a9049054013a1b77]*/ diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 31596e0bc7a31d..7fb48516e2eae7 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3560,8 +3560,8 @@ dummy_func( next_instr->op.code != ENTER_EXECUTOR) { /* Back up over EXTENDED_ARGs so executor is inserted at the correct place */ _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + // gh-152192: count with a temporary. oparg must stay intact, it's passed to the tracer below + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 0da86abed67f63..4b472aecc5fc3a 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -8581,8 +8581,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, @@ -11572,8 +11571,7 @@ (this_instr->op.code == JUMP_BACKWARD_JIT || is_resume)) && next_instr->op.code != ENTER_EXECUTOR) { _Py_CODEUNIT *insert_exec_at = this_instr; - while (oparg > 255) { - oparg >>= 8; + for (int tmp = oparg; tmp > 255; tmp >>= 8) { insert_exec_at--; } int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at,