diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 176e8f3f9f601c..bb23d5f012c8a0 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -38,3 +38,8 @@ Pending removal in Python 3.20 - :mod:`zlib` (Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.) + +* The ``__getformat__()`` class method of the :class:`float` is deprecated + and will be removed in Python 3.20. + + (Contributed by Sergey B Kirpichev in :gh:`145633`.) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 0973c387a1e595..2c4281d76118a7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1621,6 +1621,11 @@ New deprecations * Deprecated :func:`!typing.no_type_check_decorator` has been removed. (Contributed by Nikita Sobolev in :gh:`133601`.) +* The ``__getformat__()`` class method of the :class:`float` is deprecated + and will be removed in Python 3.20. + + (Contributed by Sergey B Kirpichev in :gh:`145633`.) + * ``__version__`` * The ``__version__``, ``version`` and ``VERSION`` attributes have been diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 219fbb4bb1bbe2..fadb90063d674b 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -195,11 +195,6 @@ def collect_locale(info_add): info_add('locale.getencoding', locale.getencoding()) -def collect_builtins(info_add): - info_add('builtins.float.float_format', float.__getformat__("float")) - info_add('builtins.float.double_format', float.__getformat__("double")) - - def collect_urandom(info_add): import os @@ -1050,7 +1045,6 @@ def collect_info(info): # its state. collect_urandom, - collect_builtins, collect_cc, collect_curses, collect_datetime, diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 3da662b0c4d50a..cafe9ae9e56444 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -518,9 +518,41 @@ def dec(*args, **kwargs): # for a discussion of this number. SOCK_MAX_SIZE = 16 * 1024 * 1024 + 1 +def _have_ieee_doubles(): + import math + import struct + # Check parameters for encoding of floats; a quick exit + # if they aren't same as for IEC 60559 doubles. Check + # also that subnormals are present. + if (struct.calcsize('d') != 8 + or sys.float_info.radix != 2 + or sys.float_info.mant_dig != 53 + or sys.float_info.dig != 15 + or sys.float_info.min_exp != -1021 + or sys.float_info.min_10_exp != -307 + or sys.float_info.max_exp != 1024 + or sys.float_info.max_10_exp != 308 + or not math.issubnormal(math.nextafter(0, 1))): + return False + try: + import ctypes + except ImportError: + return True + # We attempt to determine if this machine is using IEC + # floating-point formats by peering at the bits of some + # carefully chosen value. Assume that integer and + # floating-point types have same endianness. + d = 9006104071832581.0 + be_d = int.from_bytes(b"\x43\x3f\xff\x01\x02\x03\x04\x05") + dp = ctypes.pointer(ctypes.c_double(d)) + lp = ctypes.cast(dp, ctypes.POINTER(ctypes.c_uint64)) + return lp[0] == be_d + +HAVE_IEEE_754 = _have_ieee_doubles() + # decorator for skipping tests on non-IEEE 754 platforms requires_IEEE_754 = unittest.skipUnless( - float.__getformat__("double").startswith("IEEE"), + HAVE_IEEE_754, "test requires IEEE 754 doubles") def requires_zlib(reason='requires zlib'): diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 8b25607b6d504f..0b469eb59a1dc4 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -7,6 +7,7 @@ from test.test_capi.test_getargs import (Float, FloatSubclass, FloatSubclass2, BadIndex2, BadFloat2, Index, BadIndex, BadFloat) +from test import support from test.support import import_helper _testcapi = import_helper.import_module('_testcapi') @@ -23,7 +24,6 @@ 8: 2.0 ** -53, # binary64 } -HAVE_IEEE_754 = float.__getformat__("double").startswith("IEEE") INF = float("inf") NAN = float("nan") @@ -170,14 +170,13 @@ def test_unpack(self): self.assertEqual(unpack(b'\x00\x00\x00\x00\x00\x00\xf8?', LITTLE_ENDIAN), 1.5) + @support.requires_IEEE_754 def test_pack_unpack_roundtrip(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack large = 2.0 ** 100 - values = [1.0, 1.5, large, 1.0/7, math.pi] - if HAVE_IEEE_754: - values.extend((INF, NAN)) + values = [1.0, 1.5, large, 1.0/7, math.pi, INF, NAN] for value in values: for size in (2, 4, 8,): if size == 2 and value == large: @@ -196,7 +195,7 @@ def test_pack_unpack_roundtrip(self): else: self.assertEqual(value2, value) - @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") + @support.requires_IEEE_754 def test_pack_unpack_roundtrip_for_nans(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack @@ -228,7 +227,7 @@ def test_pack_unpack_roundtrip_for_nans(self): self.assertTrue(math.isnan(value)) self.assertEqual(data1, data2) - @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") + @support.requires_IEEE_754 @unittest.skipUnless(sys.maxsize != 2147483647, "requires 64-bit mode") def test_pack_unpack_nans_for_different_formats(self): pack = _testcapi.float_pack diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index c03b0a09f71889..ebe0b04738eab2 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -670,17 +670,6 @@ def __neg__(self): self.assertFalse(f >= i) -@unittest.skipUnless(hasattr(float, "__getformat__"), "requires __getformat__") -class FormatFunctionsTestCase(unittest.TestCase): - def test_getformat(self): - self.assertIn(float.__getformat__('double'), - ['unknown', 'IEEE, big-endian', 'IEEE, little-endian']) - self.assertIn(float.__getformat__('float'), - ['unknown', 'IEEE, big-endian', 'IEEE, little-endian']) - self.assertRaises(ValueError, float.__getformat__, 'chicken') - self.assertRaises(TypeError, float.__getformat__, 1) - - BE_DOUBLE_INF = b'\x7f\xf0\x00\x00\x00\x00\x00\x00' LE_DOUBLE_INF = bytes(reversed(BE_DOUBLE_INF)) BE_DOUBLE_NAN = b'\x7f\xf8\x00\x00\x00\x00\x00\x00' diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index fe14e7cb342c9e..76b7e3562c44f1 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -486,8 +486,6 @@ def test_builtin__qualname__(self): # builtin classmethod: self.assertEqual(dict.fromkeys.__qualname__, 'dict.fromkeys') - self.assertEqual(float.__getformat__.__qualname__, - 'float.__getformat__') # builtin staticmethod: self.assertEqual(str.maketrans.__qualname__, 'str.maketrans') @@ -509,7 +507,6 @@ def test_builtin__self__(self): # builtin classmethod: self.assertIs(dict.fromkeys.__self__, dict) - self.assertIs(float.__getformat__.__self__, float) # builtin staticmethod: self.assertIsNone(str.maketrans.__self__) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-24-17-29-19.gh-issue-145633.RtktKR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-24-17-29-19.gh-issue-145633.RtktKR.rst new file mode 100644 index 00000000000000..8d7ef38937f803 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-24-17-29-19.gh-issue-145633.RtktKR.rst @@ -0,0 +1,2 @@ +The ``__getformat__()`` class method of the :class:`float` is deprecated. +Patch by Sergey B Kirpichev. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index b78fd3ccb47972..70a4cc756793c7 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1696,6 +1696,11 @@ static PyObject * float___getformat___impl(PyTypeObject *type, const char *typestr) /*[clinic end generated code: output=2bfb987228cc9628 input=0ae1ba35d192f704]*/ { + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "float.__getformat__() is deprecated")) + { + return NULL; + } if (strcmp(typestr, "double") != 0 && strcmp(typestr, "float") != 0) { PyErr_SetString(PyExc_ValueError, "__getformat__() argument 1 must be "