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 objectsn
andd
don’t have to be Python ints, but they must support__index__
. -
Within the standard library,
int
,fractions.Fraction
,float
anddecimal.Decimal
would 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 thetime
module, see bpo-35707. -
The specification of
numbers.Rational
would be changed to say that rationals must have such a__ratio__
method. Thenumerator
anddenominator
properties are no longer required (but the expectation is that existing classes will keep them). -
numbers.Rational
would have a default implementation of__ratio__
returning(self.numerator, self.denominator)
. Note that this automatically makes__ratio__
work forfractions.Fraction
and 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.Fraction
would 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.Rational
without actually inheriting should implement__ratio__
. -
Code checking for
numbers.Rational
and accessing thenumerator
/denominator
properties should useoperator.ratio()
instead.