Permalink
Cannot retrieve contributors at this time
557 lines (414 sloc)
17.8 KB
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| PEP: 3141 | |
| Title: A Type Hierarchy for Numbers | |
| Version: $Revision$ | |
| Last-Modified: $Date$ | |
| Author: Jeffrey Yasskin <jyasskin@google.com> | |
| Status: Final | |
| Type: Standards Track | |
| Content-Type: text/x-rst | |
| Created: 23-Apr-2007 | |
| Python-Version: 3.0 | |
| Post-History: 25-Apr-2007, 16-May-2007, 02-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". The hierarchy is 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. | |
| The type checks in the standard library should use these classes | |
| instead of the concrete built-ins. | |
| 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=ABCMeta): pass | |
| 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: conversion to complex, bool(), .real, .imag, | |
| +, -, *, /, **, abs(), .conjugate(), ==, and !=. | |
| If it is given heterogeneous 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.""" | |
| def __bool__(self): | |
| """True if self != 0.""" | |
| return self != 0 | |
| @abstractproperty | |
| def real(self): | |
| """Retrieve the real component of this number. | |
| This should subclass Real. | |
| """ | |
| raise NotImplementedError | |
| @abstractproperty | |
| def imag(self): | |
| """Retrieve the real component of this number. | |
| This should subclass Real. | |
| """ | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __add__(self, other): | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __radd__(self, other): | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __neg__(self): | |
| raise NotImplementedError | |
| def __pos__(self): | |
| """Coerces self to whatever class defines the method.""" | |
| raise NotImplementedError | |
| def __sub__(self, other): | |
| return self + -other | |
| def __rsub__(self, other): | |
| return -self + other | |
| @abstractmethod | |
| def __mul__(self, other): | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __rmul__(self, other): | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __div__(self, other): | |
| """a/b; should promote to float or complex when necessary.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __rdiv__(self, other): | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __pow__(self, exponent): | |
| """a**b; should promote to float or complex when necessary.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __rpow__(self, base): | |
| 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 | |
| # __ne__ is inherited from object and negates whatever __eq__ does. | |
| 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 except for NaNs (which this PEP basically ignores). :: | |
| class Real(Complex): | |
| """To Complex, Real adds the operations that work on real numbers. | |
| In short, those are: conversion to float, trunc(), math.floor(), | |
| math.ceil(), round(), divmod(), //, %, <, <=, >, and >=. | |
| Real also provides defaults for some of the derived operations. | |
| """ | |
| # XXX What to do about the __int__ implementation that's | |
| # currently present on float? Get rid of it? | |
| @abstractmethod | |
| def __float__(self): | |
| """Any Real can be converted to a native float object.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __trunc__(self): | |
| """Truncates self to an Integral. | |
| Returns an Integral i such that: | |
| * i>=0 iff self>0; | |
| * abs(i) <= abs(self); | |
| * for any Integral j satisfying the first two conditions, | |
| abs(i) >= abs(j) [i.e. i has "maximal" abs among those]. | |
| i.e. "truncate towards 0". | |
| """ | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __floor__(self): | |
| """Finds the greatest Integral <= self.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __ceil__(self): | |
| """Finds the least Integral >= self.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __round__(self, ndigits:Integral=None): | |
| """Rounds self to ndigits decimal places, defaulting to 0. | |
| If ndigits is omitted or None, returns an Integral, | |
| otherwise returns a Real, preferably of the same type as | |
| self. Types may choose which direction to round half. For | |
| example, float rounds half toward even. | |
| """ | |
| raise NotImplementedError | |
| def __divmod__(self, other): | |
| """The pair (self // other, self % other). | |
| Sometimes this can be computed faster than the pair of | |
| operations. | |
| """ | |
| return (self // other, self % other) | |
| def __rdivmod__(self, other): | |
| """The pair (self // other, self % other). | |
| Sometimes this can be computed faster than the pair of | |
| operations. | |
| """ | |
| return (other // self, other % self) | |
| @abstractmethod | |
| def __floordiv__(self, other): | |
| """The floor() of self/other. Integral.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __rfloordiv__(self, other): | |
| """The floor() of other/self.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __mod__(self, other): | |
| """self % other | |
| See | |
| https://mail.python.org/pipermail/python-3000/2006-May/001735.html | |
| and consider using "self/other - trunc(self/other)" | |
| instead if you're worried about round-off errors. | |
| """ | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __rmod__(self, other): | |
| """other % self""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __lt__(self, other): | |
| """< on Reals defines a total ordering, except perhaps for NaN.""" | |
| raise NotImplementedError | |
| @abstractmethod | |
| def __le__(self, other): | |
| raise NotImplementedError | |
| # __gt__ and __ge__ are automatically done by reversing the arguments. | |
| # (But __le__ is not computed as the opposite of __gt__!) | |
| # Concrete implementations of Complex abstract methods. | |
| # Subclasses may override these, but don't have to. | |
| def __complex__(self): | |
| return complex(float(self)) | |
| @property | |
| def real(self): | |
| return +self | |
| @property | |
| def imag(self): | |
| return 0 | |
| def conjugate(self): | |
| """Conjugate is a no-op for Reals.""" | |
| return +self | |
| We should clean up Demo/classes/Rat.py and promote it into | |
| rational.py in the standard library. Then it will implement the | |
| Rational ABC. :: | |
| class Rational(Real, Exact): | |
| """.numerator and .denominator should be in lowest terms.""" | |
| @abstractproperty | |
| def numerator(self): | |
| raise NotImplementedError | |
| @abstractproperty | |
| def denominator(self): | |
| raise NotImplementedError | |
| # Concrete implementation of Real's conversion to float. | |
| # (This invokes Integer.__div__().) | |
| 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 | |
| def __index__(self): | |
| """__index__() exists because float has __int__().""" | |
| return int(self) | |
| def __lshift__(self, other): | |
| return int(self) << int(other) | |
| def __rlshift__(self, other): | |
| return int(other) << int(self) | |
| def __rshift__(self, other): | |
| return int(self) >> int(other) | |
| def __rrshift__(self, other): | |
| return int(other) >> int(self) | |
| def __and__(self, other): | |
| return int(self) & int(other) | |
| def __rand__(self, other): | |
| return int(other) & int(self) | |
| def __xor__(self, other): | |
| return int(self) ^ int(other) | |
| def __rxor__(self, other): | |
| return int(other) ^ int(self) | |
| def __or__(self, other): | |
| return int(self) | int(other) | |
| def __ror__(self, other): | |
| return int(other) | int(self) | |
| def __invert__(self): | |
| return ~int(self) | |
| # Concrete implementations of Rational and Real abstract methods. | |
| def __float__(self): | |
| """float(self) == float(int(self))""" | |
| return float(int(self)) | |
| @property | |
| def numerator(self): | |
| """Integers are their own numerators.""" | |
| return +self | |
| @property | |
| def denominator(self): | |
| """Integers have a denominator of 1.""" | |
| return 1 | |
| Changes to operations and __magic__ methods | |
| ------------------------------------------- | |
| To support more precise narrowing from float to int (and more | |
| generally, from Real to Integral), we propose the following new | |
| __magic__ methods, to be called from the corresponding library | |
| functions. All of these return Integrals rather than Reals. | |
| 1. ``__trunc__(self)``, called from a new builtin ``trunc(x)``, which | |
| returns the Integral closest to ``x`` between 0 and ``x``. | |
| 2. ``__floor__(self)``, called from ``math.floor(x)``, which returns | |
| the greatest Integral ``<= x``. | |
| 3. ``__ceil__(self)``, called from ``math.ceil(x)``, which returns the | |
| least Integral ``>= x``. | |
| 4. ``__round__(self)``, called from ``round(x)``, which returns the | |
| Integral closest to ``x``, rounding half as the type chooses. | |
| ``float`` will change in 3.0 to round half toward even. There is | |
| also a 2-argument version, ``__round__(self, ndigits)``, called | |
| from ``round(x, ndigits)``, which should return a Real. | |
| In 2.6, ``math.floor``, ``math.ceil``, and ``round`` will continue to | |
| return floats. | |
| The ``int()`` conversion implemented by ``float`` is equivalent to | |
| ``trunc()``. In general, the ``int()`` conversion should try | |
| ``__int__()`` first and if it is not found, try ``__trunc__()``. | |
| ``complex.__{divmod,mod,floordiv,int,float}__`` also go away. It would | |
| be nice to provide a nice error message to help confused porters, but | |
| not appearing in ``help(complex)`` is more important. | |
| 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. We 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 we 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. | |
| The Decimal Type | |
| ================ | |
| After consultation with its authors it has been decided that the | |
| ``Decimal`` type should not at this time be made part of the numeric | |
| tower. | |
| References | |
| ========== | |
| .. [#classtree] Possible Python 3K Class Tree?, wiki page by Bill Janssen | |
| (http://wiki.python.org/moin/AbstractBaseClasses) | |
| .. [#numericprelude] NumericPrelude: An experimental alternative hierarchy | |
| of numeric type classes | |
| (https://archives.haskell.org/code.haskell.org/numeric-prelude/docs/html/index.html) | |
| .. [#schemetower] The Scheme numerical tower | |
| (https://groups.csail.mit.edu/mac/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50) | |
| Acknowledgements | |
| ================ | |
| Thanks to Neal 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: |