summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2020-02-06 07:13:38 -0800
committerGitHub <noreply@github.com>2020-02-06 07:13:38 -0800
commit0d03a1028200646479ef9bb0ad8973d0e73f9525 (patch)
tree98bdca24fac5745d2f0802d37499d35769cc9005
parent708f472dd92f4f46c27ace710492da65da4a3319 (diff)
downloadcpython-git-0d03a1028200646479ef9bb0ad8973d0e73f9525.tar.gz
bpo-39274: Ensure Fraction.__bool__() returns a bool (GH-18017)
Some numerator types used (specifically NumPy) decides to not return a Python boolean for the "a != b" operation. Using the equivalent call to bool() guarantees a bool return also for such types. (cherry picked from commit 427c84f13f7719e6014a21bd1b81efdc02a046fb) Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
-rw-r--r--Lib/fractions.py4
-rw-r--r--Lib/test/test_fractions.py37
-rw-r--r--Misc/NEWS.d/next/Library/2020-01-15-23-13-03.bpo-39274.lpc0-n.rst1
3 files changed, 41 insertions, 1 deletions
diff --git a/Lib/fractions.py b/Lib/fractions.py
index e774d58e40..e4fcc8901b 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -636,7 +636,9 @@ class Fraction(numbers.Rational):
def __bool__(a):
"""a != 0"""
- return a._numerator != 0
+ # bpo-39274: Use bool() because (a._numerator != 0) can return an
+ # object which is not a bool.
+ return bool(a._numerator)
# support for pickling, copy, and deepcopy
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 18ab28cfeb..86b49f30f5 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -6,6 +6,7 @@ import math
import numbers
import operator
import fractions
+import functools
import sys
import unittest
import warnings
@@ -346,6 +347,42 @@ class FractionTest(unittest.TestCase):
self.assertTypedEquals(0.1+0j, complex(F(1,10)))
+ def testBoolGuarateesBoolReturn(self):
+ # Ensure that __bool__ is used on numerator which guarantees a bool
+ # return. See also bpo-39274.
+ @functools.total_ordering
+ class CustomValue:
+ denominator = 1
+
+ def __init__(self, value):
+ self.value = value
+
+ def __bool__(self):
+ return bool(self.value)
+
+ @property
+ def numerator(self):
+ # required to preserve `self` during instantiation
+ return self
+
+ def __eq__(self, other):
+ raise AssertionError("Avoid comparisons in Fraction.__bool__")
+
+ __lt__ = __eq__
+
+ # We did not implement all abstract methods, so register:
+ numbers.Rational.register(CustomValue)
+
+ numerator = CustomValue(1)
+ r = F(numerator)
+ # ensure the numerator was not lost during instantiation:
+ self.assertIs(r.numerator, numerator)
+ self.assertIs(bool(r), True)
+
+ numerator = CustomValue(0)
+ r = F(numerator)
+ self.assertIs(bool(r), False)
+
def testRound(self):
self.assertTypedEquals(F(-200), round(F(-150), -2))
self.assertTypedEquals(F(-200), round(F(-250), -2))
diff --git a/Misc/NEWS.d/next/Library/2020-01-15-23-13-03.bpo-39274.lpc0-n.rst b/Misc/NEWS.d/next/Library/2020-01-15-23-13-03.bpo-39274.lpc0-n.rst
new file mode 100644
index 0000000000..4c398682b9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-01-15-23-13-03.bpo-39274.lpc0-n.rst
@@ -0,0 +1 @@
+``bool(fraction.Fraction)`` now returns a boolean even if (numerator != 0) does not return a boolean (ex: numpy number).