2007-04-25 16:42:59 -04:00
|
|
|
|
PEP: 3141
|
2007-05-16 20:23:41 -04:00
|
|
|
|
Title: A Type Hierarchy for Numbers
|
2007-04-25 16:42:59 -04:00
|
|
|
|
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
|
2007-05-16 20:37:09 -04:00
|
|
|
|
Post-History: 25-Apr-2007, 16-May-2007
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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]_.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
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
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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``.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
Exact vs. Inexact Classes
|
|
|
|
|
-------------------------
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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."""
|
2007-04-25 16:42:59 -04:00
|
|
|
|
return hash(complex(self))
|
|
|
|
|
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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.)::
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
class Real(Complex, metaclass=TotallyOrderedABC):
|
|
|
|
|
@abstractmethod
|
2007-04-25 16:42:59 -04:00
|
|
|
|
def __float__(self):
|
2007-05-16 20:23:41 -04:00
|
|
|
|
"""Any Real can be converted to a native float object."""
|
2007-04-25 16:42:59 -04:00
|
|
|
|
raise NotImplementedError
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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):
|
2007-04-25 16:42:59 -04:00
|
|
|
|
@abstractmethod
|
|
|
|
|
def __int__(self):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
def __float__(self):
|
|
|
|
|
return float(int(self))
|
2007-05-16 20:23:41 -04:00
|
|
|
|
@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)
|
2007-04-25 16:42:59 -04:00
|
|
|
|
def __invert__(self):
|
2007-05-16 20:23:41 -04:00
|
|
|
|
return ~int(self)
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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)
|
|
|
|
|
...
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
def __hash__(self):
|
|
|
|
|
"""Surprisingly, hash() needs to be overridden too, since
|
|
|
|
|
there are integers that float can't represent."""
|
|
|
|
|
return hash(int(self))
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
Adding More Numeric ABCs
|
|
|
|
|
------------------------
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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::
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
class MyFoo(Complex): ...
|
|
|
|
|
MyFoo.register(Real)
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
TODO(jyasskin): Check this.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
|
|
|
|
|
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.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
.. [#pep3119] Introducing Abstract Base Classes
|
2007-04-25 16:42:59 -04:00
|
|
|
|
(http://www.python.org/dev/peps/pep-3119/)
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
.. [#pep3107] Function Annotations
|
2007-04-25 16:42:59 -04:00
|
|
|
|
(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)
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
.. [#numericprelude] NumericPrelude: An experimental alternative hierarchy of numeric type classes
|
2007-04-25 16:42:59 -04:00
|
|
|
|
(http://darcs.haskell.org/numericprelude/docs/html/index.html)
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
.. [#schemetower] The Scheme numerical tower
|
|
|
|
|
(http://www.swiss.ai.mit.edu/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50)
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
Acknowledgements
|
|
|
|
|
================
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
2007-05-16 20:23:41 -04:00
|
|
|
|
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.
|
2007-04-25 16:42:59 -04:00
|
|
|
|
|
|
|
|
|
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:
|