https://github.com/python/cpython/commit/40795f20ff4ef2054a7f79be537a5ba06759c15f
commit: 40795f20ff4ef2054a7f79be537a5ba06759c15f
branch: main
author: Sergey B Kirpichev <[email protected]>
committer: vstinner <[email protected]>
date: 2026-06-30T11:57:22+02:00
summary:

gh-121249: Deprecate using F/D type codes in the struct module (#152309)

Co-authored-by: Victor Stinner <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-06-26-19-01-13.gh-issue-121249.p1SBW0.rst
M Doc/deprecations/pending-removal-in-3.21.rst
M Doc/deprecations/soft-deprecations.rst
M Doc/library/struct.rst
M Doc/whatsnew/3.16.rst
M Lib/test/test_ctypes/test_byteswap.py
M Lib/test/test_memoryview.py
M Lib/test/test_struct.py
M Modules/_struct.c

diff --git a/Doc/deprecations/pending-removal-in-3.21.rst 
b/Doc/deprecations/pending-removal-in-3.21.rst
index 18b89a20e4a2082..dbd73313b4f7779 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 d1dd2b2d8c520d6..a270052788ef2a1 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/library/struct.rst b/Doc/library/struct.rst
index 775e5b2074851b7..ed5a719028d60ef 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 e215d4ddfdf41b7..1a73a79a58b78b1 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/test/test_ctypes/test_byteswap.py 
b/Lib/test/test_ctypes/test_byteswap.py
index 6a1bae14773d277..1351b3e6bd2fa46 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))
         s = c_float_complex.__ctype_be__(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))
         s = c_double_complex.__ctype_be__(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 f71b6f53486509b..df5ab9f2c879ca3 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_struct.py b/Lib/test/test_struct.py
index edd85df633fc3bb..f9803abb3db91fa 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',
-                '<F', '<D', '<Zf', '<Zd',
-            ]:
+            for f in ['Zf', 'Zd', '>Zf', '>Zd', '<Zf', '<Zd']:
                 with self.subTest(z=z, format=f):
                     round_trip = struct.unpack(f, struct.pack(f, z))[0]
                     self.assertComplexesAreIdentical(z, round_trip)
+        z = 1+1j
+        for fmt in ['F', 'D', '>F', '>D', '<F', '<D']:
+            with self.subTest(format=fmt):
+                with warnings.catch_warnings():
+                    warnings.simplefilter("error", DeprecationWarning)
+                    self.assertRaises(DeprecationWarning, struct.pack, fmt, z)
+                with warnings.catch_warnings():
+                    with self.assertWarns(DeprecationWarning):
+                        b = struct.pack(fmt, z)
+
+                with warnings.catch_warnings():
+                    warnings.simplefilter("error", DeprecationWarning)
+                    self.assertRaises(DeprecationWarning, struct.unpack, fmt, 
b)
+                with self.assertWarns(DeprecationWarning):
+                    round_trip = struct.unpack(fmt, b)[0]
+                self.assertComplexesAreIdentical(z, round_trip)
 
     @unittest.skipIf(
         support.is_android or support.is_apple_mobile,
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 000000000000000..0e676e7774d1d7a
--- /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/Modules/_struct.c b/Modules/_struct.c
index 8c611a708d02a9d..6bf3993439cf1d7 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)

_______________________________________________
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]

Reply via email to