summaryrefslogtreecommitdiffstats
path: root/Doc/library/numbers.rst
diff options
context:
space:
mode:
authorJeffrey Yasskin <jyasskin@gmail.com>2008-01-31 07:44:11 (GMT)
committerJeffrey Yasskin <jyasskin@gmail.com>2008-01-31 07:44:11 (GMT)
commitb23dea6adb7eaf3f415e05b129afa01fa1c4dd5c (patch)
treecbf4e47655cc1cc341b384d213a0fceb865876ad /Doc/library/numbers.rst
parente973c61238807dcf4ccedc18a99db8f478c422c7 (diff)
downloadcpython-b23dea6adb7eaf3f415e05b129afa01fa1c4dd5c.zip
cpython-b23dea6adb7eaf3f415e05b129afa01fa1c4dd5c.tar.gz
cpython-b23dea6adb7eaf3f415e05b129afa01fa1c4dd5c.tar.bz2
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 'Doc/library/numbers.rst')
-rw-r--r--Doc/library/numbers.rst141
1 files changed, 141 insertions, 0 deletions
diff --git a/Doc/library/numbers.rst b/Doc/library/numbers.rst
index 505a8af..6ee8f27 100644
--- a/Doc/library/numbers.rst
+++ b/Doc/library/numbers.rst
@@ -99,3 +99,144 @@ The numeric tower
3-argument form of :func:`pow`, and the bit-string operations: ``<<``,
``>>``, ``&``, ``^``, ``|``, ``~``. Provides defaults for :func:`float`,
:attr:`Rational.numerator`, and :attr:`Rational.denominator`.
+
+
+Notes for type implementors
+---------------------------
+
+Implementors should be careful to make equal numbers equal and hash
+them to the same values. This may be subtle if there are two different
+extensions of the real numbers. For example, :class:`rational.Rational`
+implements :func:`hash` as follows::
+
+ def __hash__(self):
+ if self.denominator == 1:
+ # Get integers right.
+ return hash(self.numerator)
+ # Expensive check, but definitely correct.
+ if self == float(self):
+ return hash(float(self))
+ else:
+ # Use tuple's hash to avoid a high collision rate on
+ # simple fractions.
+ return hash((self.numerator, self.denominator))
+
+
+Adding More Numeric ABCs
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are, of course, more possible ABCs for numbers, and this would
+be a poor hierarchy if it precluded the possibility of adding
+those. You can add ``MyFoo`` between :class:`Complex` and
+:class:`Real` with::
+
+ class MyFoo(Complex): ...
+ MyFoo.register(Real)
+
+
+Implementing the arithmetic operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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. For subtypes of :class:`Integral`, this
+means that :meth:`__add__` and :meth:`__radd__` should be defined as::
+
+ class MyIntegral(Integral):
+
+ def __add__(self, other):
+ if isinstance(other, MyIntegral):
+ return do_my_adding_stuff(self, other)
+ elif isinstance(other, OtherTypeIKnowAbout):
+ return do_my_other_adding_stuff(self, other)
+ else:
+ return NotImplemented
+
+ def __radd__(self, other):
+ if isinstance(other, MyIntegral):
+ return do_my_adding_stuff(other, self)
+ elif isinstance(other, OtherTypeIKnowAbout):
+ return do_my_other_adding_stuff(other, self)
+ elif isinstance(other, Integral):
+ return int(other) + int(self)
+ 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 operation on subclasses
+of :class:`Complex`. I'll refer to all of the above code that doesn't
+refer to ``MyIntegral`` and ``OtherTypeIKnowAbout`` as
+"boilerplate". ``a`` will be an instance of ``A``, which is a subtype
+of :class:`Complex` (``a : A <: Complex``), and ``b : B <:
+Complex``. I'll consider ``a + b``:
+
+ 1. If ``A`` defines an :meth:`__add__` which accepts ``b``, all is
+ well.
+ 2. If ``A`` falls back to the boilerplate code, and it were to
+ return a value from :meth:`__add__`, we'd miss the possibility
+ that ``B`` defines a more intelligent :meth:`__radd__`, so the
+ boilerplate should return :const:`NotImplemented` from
+ :meth:`__add__`. (Or ``A`` may not implement :meth:`__add__` at
+ all.)
+ 3. Then ``B``'s :meth:`__radd__` gets a chance. If it accepts
+ ``a``, all is well.
+ 4. If it falls back to the boilerplate, there are no more possible
+ methods to try, so this is where the default implementation
+ should live.
+ 5. If ``B <: A``, Python tries ``B.__radd__`` before
+ ``A.__add__``. This is ok, because it was implemented with
+ knowledge of ``A``, so it can handle those instances before
+ delegating to :class:`Complex`.
+
+If ``A<:Complex`` and ``B<:Real`` without sharing any other knowledge,
+then the appropriate shared operation is the one involving the built
+in :class:`complex`, and both :meth:`__radd__` s land there, so ``a+b
+== b+a``.
+
+Because most of the operations on any given type will be very similar,
+it can be useful to define a helper function which generates the
+forward and reverse instances of any given operator. For example,
+:class:`rational.Rational` uses::
+
+ def _operator_fallbacks(monomorphic_operator, fallback_operator):
+ def forward(a, b):
+ if isinstance(b, (int, long, Rational)):
+ return monomorphic_operator(a, b)
+ elif isinstance(b, float):
+ return fallback_operator(float(a), b)
+ elif isinstance(b, complex):
+ return fallback_operator(complex(a), b)
+ else:
+ return NotImplemented
+ forward.__name__ = '__' + fallback_operator.__name__ + '__'
+ forward.__doc__ = monomorphic_operator.__doc__
+
+ def reverse(b, a):
+ if isinstance(a, RationalAbc):
+ # Includes ints.
+ return monomorphic_operator(a, b)
+ elif isinstance(a, numbers.Real):
+ return fallback_operator(float(a), float(b))
+ elif isinstance(a, numbers.Complex):
+ return fallback_operator(complex(a), complex(b))
+ else:
+ return NotImplemented
+ reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
+ reverse.__doc__ = monomorphic_operator.__doc__
+
+ return forward, reverse
+
+ def _add(a, b):
+ """a + b"""
+ return Rational(a.numerator * b.denominator +
+ b.numerator * a.denominator,
+ a.denominator * b.denominator)
+
+ __add__, __radd__ = _operator_fallbacks(_add, operator.add)
+
+ # ... \ No newline at end of file