From 1de7a4459201dd25a5dcc1ad352e0b97e5a34773 Mon Sep 17 00:00:00 2001 From: Joshua Herman <30265+zitterbewegung@users.noreply.github.com> Date: Mon, 20 May 2024 16:02:07 -0400 Subject: [PATCH 1/5] Fix the fraction module so that __rpow__ works on arbitrary classes. The fraction module implicitly casted float if __rpow__ was implemented in your class when raising a fraction to a power. Now it isn't done but will raise an exception if __rpow__ isn't implemented. --- Doc/library/functions.rst | 10 +++++++--- Lib/fractions.py | 4 +++- Misc/ACKS | 1 + .../2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 0c7ef67774cd05..3986ace02b561a 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -608,9 +608,13 @@ are always available. They are listed here in alphabetical order. will be used for both the global and the local variables. If *globals* and *locals* are given, they are used for the global and local variables, respectively. If provided, *locals* can be any mapping object. Remember - that at the module level, globals and locals are the same dictionary. If exec - gets two separate objects as *globals* and *locals*, the code will be - executed as if it were embedded in a class definition. + that at the module level, globals and locals are the same dictionary. + + .. note:: + + Most users should just pass a *globals* argument and never *locals*. + If exec gets two separate objects as *globals* and *locals*, the code + will be executed as if it were embedded in a class definition. If the *globals* dictionary does not contain a value for the key ``__builtins__``, a reference to the dictionary of the built-in module diff --git a/Lib/fractions.py b/Lib/fractions.py index f8c6c9c438c737..e71ef58f18b3db 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -875,8 +875,10 @@ def __pow__(a, b): # A fractional power will generally produce an # irrational number. return float(a) ** float(b) - else: + elif isinstance(b, (float, complex)): return float(a) ** b + else: + return NotImplemented def __rpow__(b, a): """a ** b""" diff --git a/Misc/ACKS b/Misc/ACKS index eaa7453aaade3e..a7cd7c9992c91f 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -751,6 +751,7 @@ Kasun Herath Chris Herborth Ivan Herman Jürgen Hermann +Joshua Jay Herman Gary Herron Ernie Hershey Thomas Herve diff --git a/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst b/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst new file mode 100644 index 00000000000000..c39ccc0fdce61a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst @@ -0,0 +1,3 @@ +This fixes a situation where raising a class with rpow in the class calling +the exponent operator to raise it to the power of that class to not cast it +as a float. From 62e654f65264f78c5d541620dfe69409642b046c Mon Sep 17 00:00:00 2001 From: Joshua Herman <30265+zitterbewegung@users.noreply.github.com> Date: Sat, 25 May 2024 16:12:39 -0500 Subject: [PATCH 2/5] Update 2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst --- .../Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst b/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst index c39ccc0fdce61a..f417fcf98c04a2 100644 --- a/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst +++ b/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst @@ -1,3 +1 @@ -This fixes a situation where raising a class with rpow in the class calling -the exponent operator to raise it to the power of that class to not cast it -as a float. +When using the ** operator with a Fraction as a base and an object that implements __rpow__ as an exponent the fraction it is now not cast to a float. From 1265604de6a1354b3213bc4ed36f6b4b2b9aee00 Mon Sep 17 00:00:00 2001 From: Joshua Herman <30265+zitterbewegung@users.noreply.github.com> Date: Tue, 28 May 2024 20:46:24 -0500 Subject: [PATCH 3/5] Adding passing tests --- Lib/test/test_fractions.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 3a714c64278847..8f66dabeae9cc6 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1,5 +1,3 @@ -"""Tests for Lib/fractions.py.""" - import cmath from decimal import Decimal from test.support import requires_IEEE_754 @@ -922,21 +920,21 @@ def testMixedPower(self): self.assertTypedEquals(Root(4) ** F(2, 1), Root(4, F(1))) self.assertTypedEquals(Root(4) ** F(-2, 1), Root(4, -F(1))) self.assertTypedEquals(Root(4) ** F(-2, 3), Root(4, -3.0)) - self.assertEqual(F(3, 2) ** SymbolicReal('X'), SymbolicReal('1.5 ** X')) + self.assertEqual(F(3, 2) ** SymbolicReal('X'), SymbolicReal('3/2 ** X')) self.assertEqual(SymbolicReal('X') ** F(3, 2), SymbolicReal('X ** 1.5')) - self.assertTypedEquals(F(3, 2) ** Rect(2, 0), Polar(2.25, 0.0)) - self.assertTypedEquals(F(1, 1) ** Rect(2, 3), Polar(1.0, 0.0)) + self.assertTypedEquals(F(3, 2) ** Rect(2, 0), Polar(F(9,4), 0.0)) + self.assertTypedEquals(F(1, 1) ** Rect(2, 3), Polar(F(1), 0.0)) self.assertTypedEquals(F(3, 2) ** RectComplex(2, 0), Polar(2.25, 0.0)) self.assertTypedEquals(F(1, 1) ** RectComplex(2, 3), Polar(1.0, 0.0)) self.assertTypedEquals(Polar(4, 2) ** F(3, 2), Polar(8.0, 3.0)) self.assertTypedEquals(Polar(4, 2) ** F(3, 1), Polar(64, 6)) self.assertTypedEquals(Polar(4, 2) ** F(-3, 1), Polar(0.015625, -6)) self.assertTypedEquals(Polar(4, 2) ** F(-3, 2), Polar(0.125, -3.0)) - self.assertEqual(F(3, 2) ** SymbolicComplex('X'), SymbolicComplex('1.5 ** X')) + self.assertEqual(F(3, 2) ** SymbolicComplex('X'), SymbolicComplex('3/2 ** X')) self.assertEqual(SymbolicComplex('X') ** F(3, 2), SymbolicComplex('X ** 1.5')) - self.assertEqual(F(3, 2) ** Symbolic('X'), Symbolic('1.5 ** X')) + self.assertEqual(F(3, 2) ** Symbolic('X'), Symbolic('3/2 ** X')) self.assertEqual(Symbolic('X') ** F(3, 2), Symbolic('X ** 1.5')) def testMixingWithDecimal(self): From 085a9d184d016dd518bf1c2c838b97e2f61bb74b Mon Sep 17 00:00:00 2001 From: Joshua Herman <30265+zitterbewegung@users.noreply.github.com> Date: Wed, 29 May 2024 12:55:56 -0500 Subject: [PATCH 4/5] Update 2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst Co-authored-by: Serhiy Storchaka --- .../Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst b/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst index f417fcf98c04a2..e5cfbcf95a0b81 100644 --- a/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst +++ b/Misc/NEWS.d/next/Library/2024-05-20-13-48-37.gh-issue-119189.dhJVs5.rst @@ -1 +1,3 @@ -When using the ** operator with a Fraction as a base and an object that implements __rpow__ as an exponent the fraction it is now not cast to a float. +When using the ``**`` operator or :func:`pow` with :class:`~fractions.Fraction` +as the base and an exponent that is not rational, a float, or a complex, the +fraction is no longer converted to a float. From b585219ac44c740baaec336ef6b07a17989f050a Mon Sep 17 00:00:00 2001 From: Joshua Herman <30265+zitterbewegung@users.noreply.github.com> Date: Wed, 29 May 2024 19:26:51 -0500 Subject: [PATCH 5/5] Update test_fractions.py Added accidental removal of comment --- Lib/test/test_fractions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index b63ae31e3b927c..b91ef856ab9569 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1,3 +1,5 @@ +"""Tests for Lib/fractions.py.""" + import cmath from decimal import Decimal from test.support import requires_IEEE_754