377 lines
13 KiB
Plaintext
377 lines
13 KiB
Plaintext
PEP: 3141
|
||
Title: A Type Hierarchy for Numbers
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Jeffrey Yasskin <jyasskin@gmail.com>
|
||
Status: Draft
|
||
Type: Standards Track
|
||
Content-Type: text/x-rst
|
||
Created: 23-Apr-2007
|
||
Post-History: 25-Apr-2007, 16-May-2007
|
||
|
||
|
||
Abstract
|
||
========
|
||
|
||
This proposal defines a hierarchy of Abstract Base Classes (ABCs) (PEP
|
||
3119) to represent number-like classes. It proposes a hierarchy of
|
||
``Number :> Complex :> Real :> Rational :> Integer`` where ``A :> B``
|
||
means "A is a supertype of B", and a pair of ``Exact``/``Inexact``
|
||
classes to capture the difference between ``floats`` and
|
||
``ints``. These types are significantly inspired by Scheme's numeric
|
||
tower [#schemetower]_.
|
||
|
||
Rationale
|
||
=========
|
||
|
||
Functions that take numbers as arguments should be able to determine
|
||
the properties of those numbers, and if and when overloading based on
|
||
types is added to the language, should be overloadable based on the
|
||
types of the arguments. For example, slicing requires its arguments to
|
||
be ``Integers``, and the functions in the ``math`` module require
|
||
their arguments to be ``Real``.
|
||
|
||
Specification
|
||
=============
|
||
|
||
This PEP specifies a set of Abstract Base Classes with default
|
||
implementations. If the reader prefers to think in terms of Roles (PEP
|
||
3133), the default implementations for (for example) the Real ABC
|
||
would be moved to a RealDefault class, with Real keeping just the
|
||
method declarations.
|
||
|
||
Although this PEP uses terminology from PEP 3119, the hierarchy is
|
||
intended to be meaningful for any systematic method of defining sets
|
||
of classes, including Interfaces. I'm also using the extra notation
|
||
from PEP 3107 (Function Annotations) to specify some types.
|
||
|
||
|
||
Exact vs. Inexact Classes
|
||
-------------------------
|
||
|
||
Floating point values may not exactly obey several of the properties
|
||
you would expect. For example, it is possible for ``(X + -X) + 3 ==
|
||
3``, but ``X + (-X + 3) == 0``. On the range of values that most
|
||
functions deal with this isn't a problem, but it is something to be
|
||
aware of.
|
||
|
||
Therefore, I define ``Exact`` and ``Inexact`` ABCs to mark whether
|
||
types have this problem. Every instance of ``Integer`` and
|
||
``Rational`` should be Exact, but ``Reals`` and ``Complexes`` may or
|
||
may not be. (Do we really only need one of these, and the other is
|
||
defined as ``not`` the first?)::
|
||
|
||
class Exact(metaclass=MetaABC): pass
|
||
class Inexact(metaclass=MetaABC): pass
|
||
|
||
|
||
Numeric Classes
|
||
---------------
|
||
|
||
We begin with a Number class to make it easy for people to be fuzzy
|
||
about what kind of number they expect. This class only helps with
|
||
overloading; it doesn't provide any operations. **Open question:**
|
||
Should it specify ``__add__``, ``__sub__``, ``__neg__``, ``__mul__``,
|
||
and ``__abs__`` like Haskell's ``Num`` class?::
|
||
|
||
class Number(metaclass=MetaABC): pass
|
||
|
||
|
||
Some types (primarily ``float``) define "Not a Number" (NaN) values
|
||
that return false for any comparison, including equality with
|
||
themselves, and are maintained through operations. Because this
|
||
doesn't work well with the Reals (which are otherwise totally ordered
|
||
by ``<``), Guido suggested we might put NaN in its own type. It is
|
||
conceivable that this can still be represented by C doubles but be
|
||
included in a different ABC at runtime. **Open issue:** Is this a good
|
||
idea?::
|
||
|
||
class NotANumber(Number):
|
||
"""Implement IEEE 754 semantics."""
|
||
def __lt__(self, other): return false
|
||
def __eq__(self, other): return false
|
||
...
|
||
def __add__(self, other): return self
|
||
def __radd__(self, other): return self
|
||
...
|
||
|
||
Complex numbers are immutable and hashable. Implementors should be
|
||
careful that they 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::
|
||
|
||
class Complex(Hashable, Number):
|
||
"""A ``Complex`` should define the operations that work on the
|
||
Python ``complex`` type. If it is given heterogenous
|
||
arguments, it may fall back on this class's definition of the
|
||
operations.addition, subtraction, negation, and
|
||
multiplication. These operators should never return a
|
||
TypeError as long as both arguments are instances of Complex
|
||
(or even just implement __complex__).
|
||
"""
|
||
@abstractmethod
|
||
def __complex__(self):
|
||
"""This operation gives the arithmetic operations a fallback.
|
||
"""
|
||
return complex(self.real, self.imag)
|
||
@property
|
||
def real(self):
|
||
return complex(self).real
|
||
@property
|
||
def imag(self):
|
||
return complex(self).imag
|
||
|
||
I define the reversed operations here so that they serve as the final
|
||
fallback for operations involving instances of Complex. **Open
|
||
issue:** Should Complex's operations check for ``isinstance(other,
|
||
Complex)``? Duck typing seems to imply that we should just try
|
||
__complex__ and succeed if it works, but stronger typing might be
|
||
justified for the operators. TODO: analyze the combinations of normal
|
||
and reversed operations with real and virtual subclasses of Complex::
|
||
|
||
def __radd__(self, other):
|
||
"""Should this catch any type errors and return
|
||
NotImplemented instead?"""
|
||
return complex(other) + complex(self)
|
||
def __rsub__(self, other):
|
||
return complex(other) - complex(self)
|
||
def __neg__(self):
|
||
return -complex(self)
|
||
def __rmul__(self, other):
|
||
return complex(other) * complex(self)
|
||
def __rdiv__(self, other):
|
||
return complex(other) / complex(self)
|
||
|
||
def __abs__(self):
|
||
return abs(complex(self))
|
||
|
||
def conjugate(self):
|
||
return complex(self).conjugate()
|
||
|
||
def __hash__(self):
|
||
"""Two "equal" values of different complex types should
|
||
hash in the same way."""
|
||
return hash(complex(self))
|
||
|
||
|
||
The ``Real`` ABC indicates that the value is on the real line, and
|
||
supports the operations of the ``float`` builtin. Real numbers are
|
||
totally ordered. (NaNs were handled above.)::
|
||
|
||
class Real(Complex, metaclass=TotallyOrderedABC):
|
||
@abstractmethod
|
||
def __float__(self):
|
||
"""Any Real can be converted to a native float object."""
|
||
raise NotImplementedError
|
||
def __complex__(self):
|
||
"""Which gives us an easy way to define the conversion to
|
||
complex."""
|
||
return complex(float(self))
|
||
@property
|
||
def real(self): return self
|
||
@property
|
||
def imag(self): return 0
|
||
|
||
def __radd__(self, other):
|
||
if isinstance(other, Real):
|
||
return float(other) + float(self)
|
||
else:
|
||
return super(Real, self).__radd__(other)
|
||
def __rsub__(self, other):
|
||
if isinstance(other, Real):
|
||
return float(other) - float(self)
|
||
else:
|
||
return super(Real, self).__rsub__(other)
|
||
def __neg__(self):
|
||
return -float(self)
|
||
def __rmul__(self, other):
|
||
if isinstance(other, Real):
|
||
return float(other) * float(self)
|
||
else:
|
||
return super(Real, self).__rmul__(other)
|
||
def __rdiv__(self, other):
|
||
if isinstance(other, Real):
|
||
return float(other) / float(self)
|
||
else:
|
||
return super(Real, self).__rdiv__(other)
|
||
def __rdivmod__(self, other):
|
||
"""Implementing divmod() for your type is sufficient to
|
||
get floordiv and mod too.
|
||
"""
|
||
if isinstance(other, Real):
|
||
return divmod(float(other), float(self))
|
||
else:
|
||
return super(Real, self).__rdivmod__(other)
|
||
def __rfloordiv__(self, other):
|
||
return divmod(other, self)[0]
|
||
def __rmod__(self, other):
|
||
return divmod(other, self)[1]
|
||
|
||
def __trunc__(self):
|
||
"""Do we want properfraction, floor, ceiling, and round?"""
|
||
return trunc(float(self))
|
||
|
||
def __abs__(self):
|
||
return abs(float(self))
|
||
|
||
There is no way to define only the reversed comparison operators, so
|
||
these operations take precedence over any defined in the other
|
||
type. :( ::
|
||
|
||
def __lt__(self, other):
|
||
"""The comparison operators in Python seem to be more
|
||
strict about their input types than other functions. I'm
|
||
guessing here that we want types to be incompatible even
|
||
if they define a __float__ operation, unless they also
|
||
declare themselves to be Real numbers.
|
||
"""
|
||
if isinstance(other, Real):
|
||
return float(self) < float(other)
|
||
else:
|
||
return NotImplemented
|
||
|
||
def __le__(self, other):
|
||
if isinstance(other, Real):
|
||
return float(self) <= float(other)
|
||
else:
|
||
return NotImplemented
|
||
|
||
def __eq__(self, other):
|
||
if isinstance(other, Real):
|
||
return float(self) == float(other)
|
||
else:
|
||
return NotImplemented
|
||
|
||
|
||
There is no built-in rational type, but it's straightforward to write,
|
||
so we provide an ABC for it::
|
||
|
||
class Rational(Real, Exact):
|
||
"""rational.numerator and rational.denominator should be in
|
||
lowest terms.
|
||
"""
|
||
@abstractmethod
|
||
@property
|
||
def numerator(self):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
@property
|
||
def denominator(self):
|
||
raise NotImplementedError
|
||
|
||
def __float__(self):
|
||
return self.numerator / self.denominator
|
||
|
||
|
||
class Integer(Rational):
|
||
@abstractmethod
|
||
def __int__(self):
|
||
raise NotImplementedError
|
||
def __float__(self):
|
||
return float(int(self))
|
||
@property
|
||
def numerator(self): return self
|
||
@property
|
||
def denominator(self): return 1
|
||
|
||
def __ror__(self, other):
|
||
return int(other) | int(self)
|
||
def __rxor__(self, other):
|
||
return int(other) ^ int(self)
|
||
def __rand__(self, other):
|
||
return int(other) & int(self)
|
||
def __rlshift__(self, other):
|
||
return int(other) << int(self)
|
||
def __rrshift__(self, other):
|
||
return int(other) >> int(self)
|
||
def __invert__(self):
|
||
return ~int(self)
|
||
|
||
def __radd__(self, other):
|
||
"""All of the Real methods need to be overridden here too
|
||
in order to get a more exact type for their results.
|
||
"""
|
||
if isinstance(other, Integer):
|
||
return int(other) + int(self)
|
||
else:
|
||
return super(Integer, self).__radd__(other)
|
||
...
|
||
|
||
def __hash__(self):
|
||
"""Surprisingly, hash() needs to be overridden too, since
|
||
there are integers that float can't represent."""
|
||
return hash(int(self))
|
||
|
||
|
||
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 ``Complex`` and ``Real`` with::
|
||
|
||
class MyFoo(Complex): ...
|
||
MyFoo.register(Real)
|
||
|
||
TODO(jyasskin): Check this.
|
||
|
||
|
||
Rejected Alternatives
|
||
=====================
|
||
|
||
The initial version of this PEP defined an algebraic hierarchy
|
||
inspired by a Haskell Numeric Prelude [#numericprelude]_ including
|
||
MonoidUnderPlus, AdditiveGroup, Ring, and Field, and mentioned several
|
||
other possible algebraic types before getting to the numbers. I had
|
||
expected this to be useful to people using vectors and matrices, but
|
||
the NumPy community really wasn't interested. The numbers then had a
|
||
much more branching structure to include things like the Gaussian
|
||
Integers and Z/nZ, which could be Complex but wouldn't necessarily
|
||
support things like division. The community decided that this was too
|
||
much complication for Python, so the proposal has been scaled back to
|
||
resemble the Scheme numeric tower much more closely.
|
||
|
||
References
|
||
==========
|
||
|
||
.. [#pep3119] Introducing Abstract Base Classes
|
||
(http://www.python.org/dev/peps/pep-3119/)
|
||
|
||
.. [#pep3107] Function Annotations
|
||
(http://www.python.org/dev/peps/pep-3107/)
|
||
|
||
.. [3] Possible Python 3K Class Tree?, wiki page created by Bill Janssen
|
||
(http://wiki.python.org/moin/AbstractBaseClasses)
|
||
|
||
.. [#numericprelude] NumericPrelude: An experimental alternative hierarchy of numeric type classes
|
||
(http://darcs.haskell.org/numericprelude/docs/html/index.html)
|
||
|
||
.. [#schemetower] The Scheme numerical tower
|
||
(http://www.swiss.ai.mit.edu/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50)
|
||
|
||
|
||
Acknowledgements
|
||
================
|
||
|
||
Thanks to Neil Norwitz for encouraging me to write this PEP in the
|
||
first place, to Travis Oliphant for pointing out that the numpy people
|
||
didn't really care about the algebraic concepts, and to Guido van
|
||
Rossum, Collin Winter, and lots of other people on the mailing list
|
||
for refining the concept.
|
||
|
||
Copyright
|
||
=========
|
||
|
||
This document has been placed in the public domain.
|
||
|
||
|
||
|
||
..
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
sentence-end-double-space: t
|
||
fill-column: 70
|
||
coding: utf-8
|
||
End:
|