diff options
author | Christian Heimes <christian@cheimes.de> | 2008-01-31 14:31:45 (GMT) |
---|---|---|
committer | Christian Heimes <christian@cheimes.de> | 2008-01-31 14:31:45 (GMT) |
commit | 7b3ce6a17ea70e2acec46122e134097ce03d044a (patch) | |
tree | 51c11d64bc07786b0e1f9d5a8aaf8336a62a8d66 /Lib/rational.py | |
parent | 4b8db419c278215ac1c79f4aac2b1453b13e8c83 (diff) | |
download | cpython-7b3ce6a17ea70e2acec46122e134097ce03d044a.zip cpython-7b3ce6a17ea70e2acec46122e134097ce03d044a.tar.gz cpython-7b3ce6a17ea70e2acec46122e134097ce03d044a.tar.bz2 |
Merged revisions 60441-60474 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk
........
r60441 | christian.heimes | 2008-01-30 12:46:00 +0100 (Wed, 30 Jan 2008) | 1 line
Removed unused var
........
r60448 | christian.heimes | 2008-01-30 18:21:22 +0100 (Wed, 30 Jan 2008) | 1 line
Fixed some references leaks in sys.
........
r60450 | christian.heimes | 2008-01-30 19:58:29 +0100 (Wed, 30 Jan 2008) | 1 line
The previous change was causing a segfault after multiple calls to Py_Initialize() and Py_Finalize().
........
r60463 | raymond.hettinger | 2008-01-30 23:17:31 +0100 (Wed, 30 Jan 2008) | 1 line
Update itertool recipes
........
r60464 | christian.heimes | 2008-01-30 23:54:18 +0100 (Wed, 30 Jan 2008) | 1 line
Bug #1234: Fixed semaphore errors on AIX 5.2
........
r60469 | raymond.hettinger | 2008-01-31 02:38:15 +0100 (Thu, 31 Jan 2008) | 6 lines
Fix defect in __ixor__ which would get the wrong
answer if the input iterable had a duplicate element
(two calls to toggle() reverse each other). Borrow
the correct code from sets.py.
........
r60470 | raymond.hettinger | 2008-01-31 02:42:11 +0100 (Thu, 31 Jan 2008) | 1 line
Missing return
........
r60471 | jeffrey.yasskin | 2008-01-31 08:44:11 +0100 (Thu, 31 Jan 2008) | 4 lines
Added more documentation on how mixed-mode arithmetic should be implemented. I
also noticed and fixed a bug in Rational's forward operators (they were
claiming all instances of numbers.Rational instead of just the concrete types).
........
Diffstat (limited to 'Lib/rational.py')
-rwxr-xr-x | Lib/rational.py | 86 |
1 files changed, 74 insertions, 12 deletions
diff --git a/Lib/rational.py b/Lib/rational.py index 8de8f23..06002a3 100755 --- a/Lib/rational.py +++ b/Lib/rational.py @@ -178,16 +178,6 @@ class Rational(RationalAbc): else: return '%s/%s' % (self.numerator, self.denominator) - """ XXX This section needs a lot more commentary - - * Explain the typical sequence of checks, calls, and fallbacks. - * Explain the subtle reasons why this logic was needed. - * It is not clear how common cases are handled (for example, how - does the ratio of two huge integers get converted to a float - without overflowing the long-->float conversion. - - """ - def _operator_fallbacks(monomorphic_operator, fallback_operator): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. @@ -195,10 +185,82 @@ class Rational(RationalAbc): Use this like: __op__, __rop__ = _operator_fallbacks(just_rational_op, operator.op) + In general, we want to implement the arithmetic operations so + that mixed-mode operations either call an implementation whose + author knew about the types of both arguments, or convert both + to the nearest built in type and do the operation there. In + Rational, that means that we define __add__ and __radd__ as: + + def __add__(self, other): + if isinstance(other, (int, Rational)): + # Do the real operation. + return Rational(self.numerator * other.denominator + + other.numerator * self.denominator, + self.denominator * other.denominator) + # float and complex don't follow this protocol, and + # Rational knows about them, so special case them. + elif isinstance(other, float): + return float(self) + other + elif isinstance(other, complex): + return complex(self) + other + else: + # Let the other type take over. + return NotImplemented + + def __radd__(self, other): + # radd handles more types than add because there's + # nothing left to fall back to. + if isinstance(other, RationalAbc): + return Rational(self.numerator * other.denominator + + other.numerator * self.denominator, + self.denominator * other.denominator) + elif isinstance(other, Real): + return float(other) + float(self) + elif isinstance(other, Complex): + return complex(other) + complex(self) + else: + return NotImplemented + + + There are 5 different cases for a mixed-type addition on + Rational. I'll refer to all of the above code that doesn't + refer to Rational, float, or complex as "boilerplate". 'r' + will be an instance of Rational, which is a subtype of + RationalAbc (r : Rational <: RationalAbc), and b : B <: + Complex. The first three involve 'r + b': + + 1. If B <: Rational, int, float, or complex, we handle + that specially, and all is well. + 2. If Rational falls back to the boilerplate code, and it + were to return a value from __add__, we'd miss the + possibility that B defines a more intelligent __radd__, + so the boilerplate should return NotImplemented from + __add__. In particular, we don't handle RationalAbc + here, even though we could get an exact answer, in case + the other type wants to do something special. + 3. If B <: Rational, Python tries B.__radd__ before + Rational.__add__. This is ok, because it was + implemented with knowledge of Rational, so it can + handle those instances before delegating to Real or + Complex. + + The next two situations describe 'b + r'. We assume that b + didn't know about Rational in its implementation, and that it + uses similar boilerplate code: + + 4. If B <: RationalAbc, then __radd_ converts both to the + builtin rational type (hey look, that's us) and + proceeds. + 5. Otherwise, __radd__ tries to find the nearest common + base ABC, and fall back to its builtin type. Since this + class doesn't subclass a concrete type, there's no + implementation to fall back to, so we need to try as + hard as possible to return an actual value, or the user + will get a TypeError. + """ def forward(a, b): - if isinstance(b, RationalAbc): - # Includes ints. + if isinstance(b, (int, Rational)): return monomorphic_operator(a, b) elif isinstance(b, float): return fallback_operator(float(a), b) |