From caa608f66c5e117412b943d6888a81fa7f7569a5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 1 May 2025 16:44:20 +0300 Subject: [PATCH 1/5] gh-72902: speedup Fraction.from_Decimal/float in typical cases --- Lib/fractions.py | 20 +++++++++---------- ...5-05-01-16-44-16.gh-issue-72902.19qwJW.rst | 3 +++ 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst diff --git a/Lib/fractions.py b/Lib/fractions.py index fa722589fb4f67c..adf7da93fd19c2b 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -335,23 +335,23 @@ def from_float(cls, f): Beware that Fraction.from_float(0.3) != Fraction(3, 10). """ - if isinstance(f, numbers.Integral): + if not isinstance(f, float): + if not isinstance(f, numbers.Integral): + raise TypeError("%s.from_float() only takes floats, not %r (%s)" % + (cls.__name__, f, type(f).__name__)) return cls(f) - elif not isinstance(f, float): - raise TypeError("%s.from_float() only takes floats, not %r (%s)" % - (cls.__name__, f, type(f).__name__)) return cls._from_coprime_ints(*f.as_integer_ratio()) @classmethod def from_decimal(cls, dec): """Converts a finite Decimal instance to a rational number, exactly.""" from decimal import Decimal - if isinstance(dec, numbers.Integral): - dec = Decimal(int(dec)) - elif not isinstance(dec, Decimal): - raise TypeError( - "%s.from_decimal() only takes Decimals, not %r (%s)" % - (cls.__name__, dec, type(dec).__name__)) + if not isinstance(dec, Decimal): + if not isinstance(dec, numbers.Integral): + raise TypeError( + "%s.from_decimal() only takes Decimals, not %r (%s)" % + (cls.__name__, dec, type(dec).__name__)) + dec = int(dec) return cls._from_coprime_ints(*dec.as_integer_ratio()) @classmethod diff --git a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst new file mode 100644 index 000000000000000..c253510ee8f0f9e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst @@ -0,0 +1,3 @@ +Optimize (~x1.40-50 speedup) :meth:`fractions.Fraction.from_Decimal` and +:meth:`fractions.Fraction.from_float` for :class:`~decimal.Decimal` and +:class:`float` inputs, respectively. Patch by Sergey B Kirpichev. From 1ea8f6a37909ec4bfa7e35e7191bd7604a9dc884 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 1 May 2025 16:55:27 +0300 Subject: [PATCH 2/5] +1 --- .../next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst index c253510ee8f0f9e..471ac8ce5c9dc58 100644 --- a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst +++ b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst @@ -1,3 +1,3 @@ -Optimize (~x1.40-50 speedup) :meth:`fractions.Fraction.from_Decimal` and +Optimize (~x1.40-50 speedup) :meth:`fractions.Fraction.from_decimal` and :meth:`fractions.Fraction.from_float` for :class:`~decimal.Decimal` and :class:`float` inputs, respectively. Patch by Sergey B Kirpichev. From bf0842d38c45d2ba339cc925fd72c92fe7dea156 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 24 May 2026 05:02:10 +0300 Subject: [PATCH 3/5] Apply suggestion from @eendebakpt Co-authored-by: Pieter Eendebak --- .../next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst index 471ac8ce5c9dc58..c97bae7497b54c2 100644 --- a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst +++ b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst @@ -1,3 +1,3 @@ -Optimize (~x1.40-50 speedup) :meth:`fractions.Fraction.from_decimal` and +Optimize (~x1.40 speedup) :meth:`fractions.Fraction.from_decimal` and :meth:`fractions.Fraction.from_float` for :class:`~decimal.Decimal` and :class:`float` inputs, respectively. Patch by Sergey B Kirpichev. From ba889d889cc9f12eca361b3b67f09d01667b47fb Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 24 May 2026 17:38:52 +0300 Subject: [PATCH 4/5] + cleanup news --- .../next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst index c97bae7497b54c2..2b7d8e448408943 100644 --- a/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst +++ b/Misc/NEWS.d/next/Library/2025-05-01-16-44-16.gh-issue-72902.19qwJW.rst @@ -1,3 +1,3 @@ -Optimize (~x1.40 speedup) :meth:`fractions.Fraction.from_decimal` and +Optimize (~x1.4 speedup) :meth:`fractions.Fraction.from_decimal` and :meth:`fractions.Fraction.from_float` for :class:`~decimal.Decimal` and :class:`float` inputs, respectively. Patch by Sergey B Kirpichev. From cbb2d3feacef15a8d1fb3d44d560689e1a1173ce Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 24 May 2026 17:45:58 +0300 Subject: [PATCH 5/5] address review: rewrite without nested if --- Lib/fractions.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index cd3134543ac1059..84e844cdd697687 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -339,24 +339,24 @@ def from_float(cls, f): Beware that Fraction.from_float(0.3) != Fraction(3, 10). """ - if not isinstance(f, float): - if not isinstance(f, numbers.Integral): - raise TypeError("%s.from_float() only takes floats, not %r (%s)" % - (cls.__name__, f, type(f).__name__)) + if isinstance(f, float): + return cls._from_coprime_ints(*f.as_integer_ratio()) + if isinstance(f, numbers.Integral): return cls(f) - return cls._from_coprime_ints(*f.as_integer_ratio()) + raise TypeError("%s.from_float() only takes floats, not %r (%s)" % + (cls.__name__, f, type(f).__name__)) @classmethod def from_decimal(cls, dec): """Converts a finite Decimal instance to a rational number, exactly.""" from decimal import Decimal - if not isinstance(dec, Decimal): - if not isinstance(dec, numbers.Integral): - raise TypeError( - "%s.from_decimal() only takes Decimals, not %r (%s)" % - (cls.__name__, dec, type(dec).__name__)) + if isinstance(dec, Decimal): + return cls._from_coprime_ints(*dec.as_integer_ratio()) + if isinstance(dec, numbers.Integral): dec = int(dec) - return cls._from_coprime_ints(*dec.as_integer_ratio()) + return cls._from_coprime_ints(*dec.as_integer_ratio()) + raise TypeError("%s.from_decimal() only takes Decimals, not %r (%s)" % + (cls.__name__, dec, type(dec).__name__)) @classmethod def _from_coprime_ints(cls, numerator, denominator, /):