]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-121249: Deprecate using F/D type codes in the struct module (#152309)
authorSergey B Kirpichev <skirpichev@gmail.com>
Tue, 30 Jun 2026 09:57:22 +0000 (12:57 +0300)
committerGitHub <noreply@github.com>
Tue, 30 Jun 2026 09:57:22 +0000 (11:57 +0200)
Co-authored-by: Victor Stinner <vstinner@python.org>
Doc/deprecations/pending-removal-in-3.21.rst
Doc/deprecations/soft-deprecations.rst
Doc/library/struct.rst
Doc/whatsnew/3.16.rst
Lib/test/test_ctypes/test_byteswap.py
Lib/test/test_memoryview.py
Lib/test/test_struct.py
Misc/NEWS.d/next/Library/2026-06-26-19-01-13.gh-issue-121249.p1SBW0.rst [new file with mode: 0644]
Modules/_struct.c

index 18b89a20e4a20827bbc611b45d1ce650f44124b9..dbd73313b4f7779fb103af9cd521510ade54b301 100644 (file)
@@ -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'``.
index d1dd2b2d8c520d6df08e7ee65209b35005637d72..a270052788ef2a1082d1dda6734f5ae698279952 100644 (file)
@@ -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`.)
index 775e5b2074851b7a6e0c8a2becab3aa4b550ea36..ed5a719028d60ef349c0a59583fe4798c6bb73e6 100644 (file)
@@ -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::
 
index e215d4ddfdf41b7870ba458462f239f027ae7266..1a73a79a58b78b14447729e971810e332d114908 100644 (file)
@@ -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
index 6a1bae14773d2775ddb52194dc485e277baa5765..1351b3e6bd2fa465515701e4743aa7a3f01921c2 100644 (file)
@@ -178,14 +178,14 @@ class Test(unittest.TestCase, StructCheckMixin):
             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 @@ class Test(unittest.TestCase, StructCheckMixin):
             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)
index f71b6f53486509b67d43c436d781253c3a441370..df5ab9f2c879ca30c32b1e18698c164ca91b1030 100644 (file)
@@ -717,8 +717,8 @@ class OtherTest(unittest.TestCase):
         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)
index edd85df633fc3bbb0c1c66389eb8af34a1840a1e..f9803abb3db91faa93dd3edb622e4c422fab7833 100644 (file)
@@ -7,6 +7,7 @@ import operator
 import unittest
 import struct
 import sys
+import warnings
 import weakref
 
 from test import support
@@ -995,14 +996,26 @@ class StructTest(ComplexesAreIdenticalMixin, unittest.TestCase):
         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 (file)
index 0000000..0e676e7
--- /dev/null
@@ -0,0 +1 @@
+Deprecate using ``'F'`` and ``'D'`` type codes in the :mod:`struct` module.
index 8c611a708d02a9dad536447e8ad3c42f8e166e89..6bf3993439cf1d7ccb622792496eb1d410d710d8 100644 (file)
@@ -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)