From 9ca0a71896171ce2e9083e48002947c4d8132899 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 26 Jun 2026 19:01:34 +0300 Subject: [PATCH 1/3] gh-121249: Deprecate using F/D type codes in the struct module --- Doc/deprecations/pending-removal-in-3.21.rst | 6 ++++ Doc/whatsnew/3.16.rst | 7 +++++ Lib/test/test_struct.py | 22 ++++++++++---- ...-06-26-19-01-13.gh-issue-121249.p1SBW0.rst | 1 + Modules/_struct.c | 29 +++++++++++++++++++ 5 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-26-19-01-13.gh-issue-121249.p1SBW0.rst diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index 18b89a20e4a208..2df276c649ba6b 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 now + deprecated. These codes will be removed in Python 3.21. Use instead + two-letter forms ``'Zf'`` and ``'Zd'``. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index ca80b0a1227588..ee26ebe40dadc3 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -485,6 +485,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 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/test/test_struct.py b/Lib/test/test_struct.py index edd85df633fc3b..bbc751e5652fa1 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,25 @@ 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', '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) From c5b52c0d3f5ff0fc22cac1c83485f4b636e5b478 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 26 Jun 2026 19:22:51 +0300 Subject: [PATCH 2/3] adjust test --- Lib/test/test_memoryview.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From f2e3f8e3997469c62a1d7bef9912285866cf4b66 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 27 Jun 2026 05:47:47 +0300 Subject: [PATCH 3/3] +1 --- Lib/test/test_ctypes/test_byteswap.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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)