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 thatq * d == n. The objectsnandddon’t have to be Python ints, but they must support__index__. -
Within the standard library,
int,fractions.Fraction,floatanddecimal.Decimalwould support__ratio__. For the latter two, this would be an alias ofas_integer_ratio. Note that__ratio__is therefore not limited tonumbers.Rational, it can be used also for exact arithmetic with floating point numbers. This may be useful for thetimemodule, see bpo-35707. -
The specification of
numbers.Rationalwould be changed to say that rationals must have such a__ratio__method. Thenumeratoranddenominatorproperties are no longer required (but the expectation is that existing classes will keep them). -
numbers.Rationalwould have a default implementation of__ratio__returning(self.numerator, self.denominator). Note that this automatically makes__ratio__work forfractions.Fractionand all other existing classes inheriting fromnumbers.Rational. -
A helper function
operator.ratio(x)is added, returningx.__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
fractions.Fractionwould useoperator.ratio().
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__ratio__. -
Code checking for
numbers.Rationaland accessing thenumerator/denominatorproperties should useoperator.ratio()instead.