Just to make it more clear what we’re talking about, could you post a link to that code?
In my view, “backwards compatibility” is about observable functionality. A good proxy for this is: it shouldn’t break any testsuites. By this definition, my proposal can be implemented in a backwards compatible way.
What’s happening here is that my proposal is adding a bug to your code. The fact is and remains that your code (unchanged) doesn’t support SageMath rationals. Right now, your code is not to blame for this. But with the proposal, your code might be blaimed.
No, it’s unpublished personal code. To avoid misunderstanding, I’m not talking about a production system that will break here - obviously I won’t suddenly be adding SageMath rationals to such code. This is a small personal project, mostly experimental (it’s playing about with continued fractions) that could well languish for a while and then I’ll pick it up and run it again. I probably also have some data analysis code from a while back that uses fractions and freely expects to use
.numerator etc. All I’m really saying is that I’m a relatively frequent user of fractions in my code, and I can’t assess the impact on my code from the partial proposal you’ve posted this far.
Ultimately, though, I don’t care at this point. Until I see a full proposal, I’m just anticipating problems. Once you have the proposal written up, then I’ll look at it and decide whether I’m comfortable with the breakage (and whether or not I want to argue with you over what counts as “breakage”, if that’s necessary).
Ok, here is a more complete proposal:
Define a new special method
__ratio__with the following meaning:
q.__ratio__()must be a 2-tuple
(n, d)of integers such that
q * d == n. The objects
ddon’t have to be Python ints, but they must support
Within the standard library,
__ratio__. For the latter two, this would be an alias of
as_integer_ratio. Note that
__ratio__is therefore not limited to
numbers.Rational, it can be used also for exact arithmetic with floating point numbers. This may be useful for the
timemodule, see bpo-35707.
The specification of
numbers.Rationalwould be changed to say that rationals must have such a
denominatorproperties are no longer required (but the expectation is that existing classes will keep them).
numbers.Rationalwould have a default implementation of
(self.numerator, self.denominator). Note that this automatically makes
fractions.Fractionand all other existing classes inheriting from
A helper function
operator.ratio(x)is added, returning
x.__ratio__()but falling back to
(x.numerator, x.denominator). This is recommended over calling
__ratio__manually. If it’s deemed useful, also a C API function will be added.
The constructor for
As far as I can see, this proposal is backwards compatible in the sense that all functionality that used to work still works. Nevertheless, existing code should be changed to support the new protocol:
Classes registering as
numbers.Rationalwithout actually inheriting should implement
Code checking for
numbers.Rationaland accessing the
denominatorproperties should use
I don’t consider this backwards compatible. Every user of .numerator etc. would have to be changed. And what’s the point? So SageMath numbers can claim compatibility with PEP 3141? Even the stdlib’s decimal module doesn’t claim to be compatible with the numbers API. I really don’t see the point of making such a change. If you need to pass a SageMath number to some API that expects the numbers API you’ll just have to convert at the boundary.
To use RFC 2119, it’s not REQUIRED for such code to change but it is RECOMMENDED (nothing that used to work will break if code doesn’t change). The first is obviously not backwards compatible, the second is in a gray zone depending on how strictly you define backwards compatibility.
The real goal is interoperability with
fractions.Fraction and currently PEP 3141 is the only means to that end. Think of it this way: we have
__index__ for converting to a Python int,
__float__ to a float and
__complex__ to a complex. But we’re missing a special method for fractions.
And what real-world problem does that solve?
There are various mathematical packages that use
fractions.Fraction which are actually used by people to do real work. For example, realalg to name just one.
Using such packages inside SageMath is more difficult than it should be because
Fraction does not support SageMath integers/rationals. Of course, one can do explicit conversions but that’s not convenient. For integers,
__index__ was invented to solve this problem.
It seems now that Python is converging towards using
as_integer_ratio for rationals. The only part that’s missing is actually using
as_integer_ratio in the
Fraction constructor, see either PR 15327 or PR 15329.