-
-
Notifications
You must be signed in to change notification settings - Fork 31k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-39274: Ensure Fraction.__bool__
returns a bool
#18017
Conversation
Lib/test/test_fractions.py
Outdated
return bool(self.value) | ||
|
||
r = F(1, 1) | ||
r._numerator = custom_value(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to avoid accessing private attribute _numerator:
numerator = custom_value(1)
r = F(numerator, 1)
...
self.assertEqual(numerator.bool_used, True)
(same below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree fixed, I just got lost a bit because I missed that numerator.numerator
is used (which loses the original type), but since its only that it seems nicer. Of course if the instantiation logic changes, there is a chance of the test needing to be revised.
56bdc21
to
89fc30c
Compare
Misc/NEWS.d/next/Library/2020-01-15-23-13-03.bpo-39274.lpc0-n.rst
Outdated
Show resolved
Hide resolved
return self | ||
|
||
numerator = custom_value(1) | ||
r = F(numerator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to explicit the denominator: F(numerator, 1). Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That does not change much, if I add that, the only difference is that I also have to define __mul__
. The only solution is going back to r._numerator = ...
(it is used elsewhere in the file)
|
||
def __bool__(self): | ||
self.bool_used = True | ||
return bool(self.value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bug is that "numerator != 0" returns an object which is not a subtype of bool. Does your test reproduce the bug if you revert the fractions.py change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I am testing that r._numerator.__bool__
is called, which is not quite the same thing of course. Other solutions to achieve the same things (e.g. bool(a._numerator != 0)
are possible and would fail the test as well).
I can add __ne__
that does the bad thing, but it would never get called. This forces to adapt the test in case the code is adapted, but without a full fledged numpy integer around, it seems hard to write a generic test that only checks for a bool
result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, tried to make it a bit more waterproof (by resetting bool_used
), if I can import NumPy – skipping the test when its not available – that may be nicer of course (but I assume that is a no-go). Otherwise it feels like I would have to implement a full fleged rational
with added strange logic...
Its over engineered, but right now I do not have a better idea TBH (unless we just skip the test...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You cannot use 3rd party modules like numpy in the Python test suite, sadly. (At least, we are trying hard to avoid that.)
So you should try to mimick numpy's bool behavior. Please just implement a ne() method which raises an AssertionError. It would make me more comfortable. Or better: implement eq and lt, and @ @functools.total_ordering:
https://docs.python.org/dev/library/functools.html
Again, these methods would raise an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next try... Had tried to avoid registering with numbers.Rational
but there is probably no reason, so now not inheriting from int
anymore, which is much cleaner (and added your error, instead of ensuring __bool__
is called).
Overall, seems much better, since now it really is a crippled object that cannot do anything but what it is supposed to do...
@property | ||
def numerator(self): | ||
# required to preserve `self` during instantiation | ||
return self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to remove that and use F(numerator, 1) instead.
08aee01
to
2413700
Compare
|
||
def __bool__(self): | ||
self.bool_used = True | ||
return bool(self.value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You cannot use 3rd party modules like numpy in the Python test suite, sadly. (At least, we are trying hard to avoid that.)
So you should try to mimick numpy's bool behavior. Please just implement a ne() method which raises an AssertionError. It would make me more comfortable. Or better: implement eq and lt, and @ @functools.total_ordering:
https://docs.python.org/dev/library/functools.html
Again, these methods would raise an exception.
Lib/test/test_fractions.py
Outdated
r = F(numerator) | ||
# ensure the numerator was not lost during instantiation: | ||
self.assertTrue(r.numerator is numerator) | ||
numerator.bool_used = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest self.assertFalse(numerator.bool_used) instead.
numerator.bool_used = False | |
self.assertFalse(numerator.bool_used) |
Lib/test/test_fractions.py
Outdated
numerator.bool_used = False | ||
self.assertTrue(bool(r) is True) | ||
# If bool(numerator) was called, a bool result should be guaranteed: | ||
self.assertEqual(numerator.bool_used, True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(numerator.bool_used, True) | |
self.assertTrue(numerator.bool_used) |
Lib/test/test_fractions.py
Outdated
# ensure the numerator was not lost during instantiation: | ||
self.assertTrue(r.numerator is numerator) | ||
numerator.bool_used = False | ||
self.assertTrue(bool(r) is True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get the value if the test fails:
self.assertTrue(bool(r) is True) | |
self.assertIs(bool(r), True) |
2413700
to
4bf428a
Compare
@seberg: Did you see my latest comments? Would you mind to update your PR? |
Some numerator types used (specifically NumPy) decides to not return a python boolean for the `!=` operation. Using the equivalent call to `bool()` guarantees a bool return also for such types. Closes bpo-39274
4bf428a
to
a465dad
Compare
Thanks for the ping, sorry, I had somehow forgotten/missed the very true comments regarding the |
Codecov Report
@@ Coverage Diff @@
## master #18017 +/- ##
==========================================
- Coverage 82.13% 82.11% -0.02%
==========================================
Files 1953 1954 +1
Lines 582680 583290 +610
Branches 44349 44407 +58
==========================================
+ Hits 478602 478993 +391
- Misses 94416 94658 +242
+ Partials 9662 9639 -23
Continue to review full report at Codecov.
|
__lt__ = __eq__ | ||
|
||
# We did not implement all abstract methods, so register: | ||
numbers.Rational.register(CustomValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may fail if you run the test with reference leak test:
./python -m test -R 3:3 test_fractions
Can you check that it does not leak? I would prefer to avoid .register() if it does leak.
... This test is getting more and more complex... Maybe we should just ignore the test and just merge the fix.
What do you think @mdickinson?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, seems to work:
% /home/sebastian/python-dev/bin/python3.9d -m test -R 3:3 test_fractions
0:00:00 load avg: 0.39 Run tests sequentially
0:00:00 load avg: 0.39 [1/1] test_fractions
beginning 6 repetitions
123456
......
== Tests result: SUCCESS ==
1 test OK.
Total duration: 221 ms
Tests result: SUCCESS
I have not used the python refcount checker, so not sure if it clears ABCs are not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix itself looks good to me; I think we should merge as is.
Ok, I was maybe too far in term of nitpicking, sorry :-) It's very difficult to implement a type implementing the numbers.Rational ABC :-( I had exactly the same problem on another fractions bug, and I decided to not add such test: https://bugs.python.org/issue39350#msg361481 |
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 427c84f) Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
GH-18376 is a backport of this pull request to the 3.8 branch. |
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 427c84f) Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
GH-18377 is a backport of this pull request to the 3.7 branch. |
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 427c84f) Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
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 427c84f) Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
Ah, nitpicking has its upsides :). If anything, maybe the next time there is a fraction issue, this actually helps writing a reasonable test for it. |
Some numerator types used (specifically NumPy) decides to not
return a python boolean for the
!=
operation. Using the equivalentcall to
bool()
guarantees a bool return also for such types.Closes bpo-39274
https://bugs.python.org/issue39274
https://bugs.python.org/issue39274