407 lines
14 KiB
Plaintext
407 lines
14 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, xx-Aug-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 :> Integral`` 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 ``Integrals``, and the functions in the ``math`` module require
|
||
their arguments to be ``Real``.
|
||
|
||
Specification
|
||
=============
|
||
|
||
This PEP specifies a set of Abstract Base Classes, and suggests a
|
||
general strategy for implementing some of the methods. It uses
|
||
terminology from PEP 3119, but the hierarchy is intended to be
|
||
meaningful for any systematic method of defining sets of classes.
|
||
|
||
|
||
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.
|
||
|
||
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. That is, ``nan + x
|
||
-> nan`` and ``nan == nan -> False`` 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 UndefinedNumber(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
|
||
...
|
||
# Should we demand a conversion to float?
|
||
|
||
Most implementations of complex numbers will be hashable, but if you
|
||
need to rely on that, you'll have to check it explicitly: mutable
|
||
numbers are supported by this hierarchy. ::
|
||
|
||
class Complex(Number):
|
||
"""Complex defines the operations that work on the builtin complex type.
|
||
|
||
In short, those are: a conversion to complex, .real, .imag, +,
|
||
-, *, /, abs(), .conjugate, ==, and !=.
|
||
|
||
If it is given heterogenous arguments, and doesn't have
|
||
special knowledge about them, it should fall back to the
|
||
builtin complex type as described below.
|
||
"""
|
||
@abstractmethod
|
||
def __complex__(self):
|
||
"""Return a builtin complex instance."""
|
||
|
||
@abstractmethod
|
||
@property
|
||
def real(self):
|
||
"""Retrieve the real component of this number, which should subclass Real."""
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
@property
|
||
def imag(self):
|
||
"""Retrieve the real component of this number, which should subclass Real."""
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def __add__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __sub__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __neg__(self):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __mul__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __div__(self, other):
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def __abs__(self):
|
||
"""Returns the Real distance from 0."""
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def conjugate(self):
|
||
"""(x+y*i).conjugate() returns (x-y*i)."""
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def __eq__(self, other):
|
||
raise NotImplementedError
|
||
def __ne__(self, other):
|
||
return not (self == other)
|
||
|
||
|
||
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):
|
||
"""To Complex, Real adds the operations that work on real numbers.
|
||
|
||
In short, those are: a conversion to float, trunc(), divmod,
|
||
%, <, <=, >, and >=.
|
||
|
||
Real also provides defaults for the derived operations.
|
||
"""
|
||
@abstractmethod
|
||
def __float__(self):
|
||
"""Any Real can be converted to a native float object."""
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def __trunc__(self):
|
||
"""Returns an Integral of the same sign as self whose abs is <= self's abs."""
|
||
raise NotImplementedError
|
||
|
||
def __divmod__(self, other):
|
||
"""The pair (self // other, self % other)."""
|
||
return (self // other, self % other)
|
||
@abstractmethod
|
||
def __floordiv__(self, other):
|
||
"""The floor() of self/other."""
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __mod__(self, other):
|
||
"""."""
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def __lt__(self, other):
|
||
raise NotImplementedError
|
||
def __le__(self, other):
|
||
# Assume that if other is Real, it defines an ordering
|
||
# consistent with this class, or returns NotImplemented.
|
||
if isinstance(other, Real):
|
||
return not (other < self)
|
||
|
||
# Concrete implementations of Complex abstract methods.
|
||
def __complex__(self):
|
||
return complex(float(self))
|
||
@property
|
||
def real(self):
|
||
return self
|
||
@property
|
||
def imag(self):
|
||
return 0
|
||
|
||
|
||
There is no built-in rational type, but it's straightforward to write,
|
||
so we provide an ABC for it. *Open issue*: Add Demo/classes/Rat.py to
|
||
the stdlib?::
|
||
|
||
class Rational(Real, Exact):
|
||
""".numerator and .denominator should be in lowest terms."""
|
||
@abstractmethod
|
||
@property
|
||
def numerator(self):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
@property
|
||
def denominator(self):
|
||
raise NotImplementedError
|
||
|
||
# Concrete implementation of Real's conversion to float.
|
||
def __float__(self):
|
||
return self.numerator / self.denominator
|
||
|
||
|
||
And finally integers::
|
||
|
||
class Integral(Rational):
|
||
"""Integral adds a conversion to int and the bit-string operations."""
|
||
@abstractmethod
|
||
def __int__(self):
|
||
raise NotImplementedError
|
||
|
||
@abstractmethod
|
||
def __lshift__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __rshift__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __and__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __xor__(self, other):
|
||
raise NotImplementedError
|
||
@abstractmethod
|
||
def __or__(self, other):
|
||
raise NotImplementedError
|
||
|
||
# Concrete implementations of Rational and Real abstract methods.
|
||
def __float__(self):
|
||
return float(int(self))
|
||
@property
|
||
def numerator(self):
|
||
return self
|
||
@property
|
||
def denominator(self):
|
||
return 1
|
||
|
||
|
||
|
||
|
||
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 ``Integral`` 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(Number): pass
|
||
class Inexact(Number): pass
|
||
|
||
|
||
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, a complex type
|
||
could reasonably implement hash() as follows::
|
||
|
||
def __hash__(self):
|
||
return hash(complex(self))
|
||
|
||
but should be careful of any values that fall outside of the built in
|
||
complex's range or precision.
|
||
|
||
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)
|
||
|
||
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 Integral, this means that
|
||
__add__ and __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 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 ``Complex`` (``a : A <:
|
||
Complex``), and ``b : B <: Complex``. I'll consider ``a + b``:
|
||
|
||
1. If A defines an __add__ which accepts b, all is well.
|
||
2. If A 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__. (Or A may not implement __add__ at
|
||
all.)
|
||
3. Then B's __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 Complex.
|
||
|
||
If ``A<:Complex`` and ``B<:Real`` without sharing any other knowledge,
|
||
then the appropriate shared operation is the one involving the built
|
||
in complex, and both __radd__s land there, so ``a+b == b+a``.
|
||
|
||
|
||
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, and we ran into the
|
||
issue that even if ``x`` is an instance of ``X <: MonoidUnderPlus``
|
||
and ``y`` is an instance of ``Y <: MonoidUnderPlus``, ``x + y`` may
|
||
still not make sense.
|
||
|
||
Then I gave the numbers 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 I've now
|
||
scaled back the proposal to resemble the Scheme numeric tower much
|
||
more closely.
|
||
|
||
|
||
References
|
||
==========
|
||
|
||
.. [#pep3119] Introducing Abstract Base Classes
|
||
(http://www.python.org/dev/peps/pep-3119/)
|
||
|
||
.. [#classtree] 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, to Alan Isaac for
|
||
reminding me that Scheme had already done this, and to Guido van
|
||
Rossum 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:
|