Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 3141 – A Type Hierarchy for Numbers

Author:
Jeffrey Yasskin <jyasskin at google.com>
Status:
Final
Type:
Standards Track
Created:
23-Apr-2007
Python-Version:
3.0
Post-History:
25-Apr-2007, 16-May-2007, 02-Aug-2007

Table of Contents

Abstract

This proposal defines a hierarchy of Abstract Base Classes (ABCs) (PEP3119) to represent number-like classes. It proposes a hierarchy ofNumber:>Complex:>Real:>Rational:>Integral whereA:>Bmeans “A is a supertype of B”. The hierarchy is inspired by Scheme’snumeric tower[2].

Rationale

Functions that take numbers as arguments should be able to determinethe properties of those numbers, and if and when overloading based ontypes is added to the language, should be overloadable based on thetypes of the arguments. For example, slicing requires its arguments tobeIntegrals, and the functions in themath module requiretheir arguments to beReal.

Specification

This PEP specifies a set of Abstract Base Classes, and suggests ageneral strategy for implementing some of the methods. It usesterminology fromPEP 3119, but the hierarchy is intended to bemeaningful for any systematic method of defining sets of classes.

The type checks in the standard library should use these classesinstead of the concrete built-ins.

Numeric Classes

We begin with a Number class to make it easy for people to be fuzzyabout what kind of number they expect. This class only helps withoverloading; it doesn’t provide any operations.

classNumber(metaclass=ABCMeta):pass

Most implementations of complex numbers will be hashable, but if youneed to rely on that, you’ll have to check it explicitly: mutablenumbers are supported by this hierarchy.

classComplex(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.    """@abstractmethoddef__complex__(self):"""Return a builtin complex instance."""def__bool__(self):"""True if self != 0."""returnself!=0@abstractpropertydefreal(self):"""Retrieve the real component of this number.        This should subclass Real.        """raiseNotImplementedError@abstractpropertydefimag(self):"""Retrieve the imaginary component of this number.        This should subclass Real.        """raiseNotImplementedError@abstractmethoddef__add__(self,other):raiseNotImplementedError@abstractmethoddef__radd__(self,other):raiseNotImplementedError@abstractmethoddef__neg__(self):raiseNotImplementedErrordef__pos__(self):"""Coerces self to whatever class defines the method."""raiseNotImplementedErrordef__sub__(self,other):returnself+-otherdef__rsub__(self,other):return-self+other@abstractmethoddef__mul__(self,other):raiseNotImplementedError@abstractmethoddef__rmul__(self,other):raiseNotImplementedError@abstractmethoddef__div__(self,other):"""a/b; should promote to float or complex when necessary."""raiseNotImplementedError@abstractmethoddef__rdiv__(self,other):raiseNotImplementedError@abstractmethoddef__pow__(self,exponent):"""a**b; should promote to float or complex when necessary."""raiseNotImplementedError@abstractmethoddef__rpow__(self,base):raiseNotImplementedError@abstractmethoddef__abs__(self):"""Returns the Real distance from 0."""raiseNotImplementedError@abstractmethoddefconjugate(self):"""(x+y*i).conjugate() returns (x-y*i)."""raiseNotImplementedError@abstractmethoddef__eq__(self,other):raiseNotImplementedError# __ne__ is inherited from object and negates whatever __eq__ does.

TheReal ABC indicates that the value is on the real line, andsupports the operations of thefloat builtin. Real numbers aretotally ordered except for NaNs (which this PEP basically ignores).

classReal(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?@abstractmethoddef__float__(self):"""Any Real can be converted to a native float object."""raiseNotImplementedError@abstractmethoddef__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".        """raiseNotImplementedError@abstractmethoddef__floor__(self):"""Finds the greatest Integral <= self."""raiseNotImplementedError@abstractmethoddef__ceil__(self):"""Finds the least Integral >= self."""raiseNotImplementedError@abstractmethoddef__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.        """raiseNotImplementedErrordef__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)@abstractmethoddef__floordiv__(self,other):"""The floor() of self/other. Integral."""raiseNotImplementedError@abstractmethoddef__rfloordiv__(self,other):"""The floor() of other/self."""raiseNotImplementedError@abstractmethoddef__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.        """raiseNotImplementedError@abstractmethoddef__rmod__(self,other):"""other % self"""raiseNotImplementedError@abstractmethoddef__lt__(self,other):"""< on Reals defines a total ordering, except perhaps for NaN."""raiseNotImplementedError@abstractmethoddef__le__(self,other):raiseNotImplementedError# __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):returncomplex(float(self))@propertydefreal(self):return+self@propertydefimag(self):return0defconjugate(self):"""Conjugate is a no-op for Reals."""return+self

We should clean up Demo/classes/Rat.py and promote it intorational.py in the standard library. Then it will implement theRational ABC.

classRational(Real,Exact):""".numerator and .denominator should be in lowest terms."""@abstractpropertydefnumerator(self):raiseNotImplementedError@abstractpropertydefdenominator(self):raiseNotImplementedError# Concrete implementation of Real's conversion to float.# (This invokes Integer.__div__().)def__float__(self):returnself.numerator/self.denominator

And finally integers:

classIntegral(Rational):"""Integral adds a conversion to int and the bit-string operations."""@abstractmethoddef__int__(self):raiseNotImplementedErrordef__index__(self):"""__index__() exists because float has __int__()."""returnint(self)def__lshift__(self,other):returnint(self)<<int(other)def__rlshift__(self,other):returnint(other)<<int(self)def__rshift__(self,other):returnint(self)>>int(other)def__rrshift__(self,other):returnint(other)>>int(self)def__and__(self,other):returnint(self)&int(other)def__rand__(self,other):returnint(other)&int(self)def__xor__(self,other):returnint(self)^int(other)def__rxor__(self,other):returnint(other)^int(self)def__or__(self,other):returnint(self)|int(other)def__ror__(self,other):returnint(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))"""returnfloat(int(self))@propertydefnumerator(self):"""Integers are their own numerators."""return+self@propertydefdenominator(self):"""Integers have a denominator of 1."""return1

Changes to operations and __magic__ methods

To support more precise narrowing from float to int (and moregenerally, from Real to Integral), we propose the following new__magic__ methods, to be called from the corresponding libraryfunctions. All of these return Integrals rather than Reals.

  1. __trunc__(self), called from a new builtintrunc(x), whichreturns the Integral closest tox between 0 andx.
  2. __floor__(self), called frommath.floor(x), which returnsthe greatest Integral<=x.
  3. __ceil__(self), called frommath.ceil(x), which returns theleast Integral>=x.
  4. __round__(self), called fromround(x), which returns theIntegral closest tox, rounding half as the type chooses.float will change in 3.0 to round half toward even. There isalso a 2-argument version,__round__(self,ndigits), calledfromround(x,ndigits), which should return a Real.

In 2.6,math.floor,math.ceil, andround will continue toreturn floats.

Theint() conversion implemented byfloat is equivalent totrunc(). In general, theint() conversion should try__int__() first and if it is not found, try__trunc__().

complex.__{divmod,mod,floordiv,int,float}__ also go away. It wouldbe nice to provide a nice error message to help confused porters, butnot appearing inhelp(complex) is more important.

Notes for type implementors

Implementors should be careful to make equal numbers equal andhash them to the same values. This may be subtle if there are twodifferent extensions of the real numbers. For example, a complex typecould reasonably implement hash() as follows:

def__hash__(self):returnhash(complex(self))

but should be careful of any values that fall outside of the built incomplex’s range or precision.

Adding More Numeric ABCs

There are, of course, more possible ABCs for numbers, and this wouldbe a poor hierarchy if it precluded the possibility of addingthose. You can addMyFoo betweenComplex andReal with:

classMyFoo(Complex):...MyFoo.register(Real)

Implementing the arithmetic operations

We want to implement the arithmetic operations so that mixed-modeoperations either call an implementation whose author knew about thetypes of both arguments, or convert both to the nearest built in typeand do the operation there. For subtypes of Integral, this means that__add__ and __radd__ should be defined as:

classMyIntegral(Integral):def__add__(self,other):ifisinstance(other,MyIntegral):returndo_my_adding_stuff(self,other)elifisinstance(other,OtherTypeIKnowAbout):returndo_my_other_adding_stuff(self,other)else:returnNotImplementeddef__radd__(self,other):ifisinstance(other,MyIntegral):returndo_my_adding_stuff(other,self)elifisinstance(other,OtherTypeIKnowAbout):returndo_my_other_adding_stuff(other,self)elifisinstance(other,Integral):returnint(other)+int(self)elifisinstance(other,Real):returnfloat(other)+float(self)elifisinstance(other,Complex):returncomplex(other)+complex(self)else:returnNotImplemented

There are 5 different cases for a mixed-type operation on subclassesof Complex. I’ll refer to all of the above code that doesn’t refer toMyIntegral and OtherTypeIKnowAbout as “boilerplate”.a will be aninstance ofA, which is a subtype ofComplex (a:A<:Complex), andb:B<:Complex. I’ll considera+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 returna value from __add__, we’d miss the possibility that B definesa more intelligent __radd__, so the boilerplate should returnNotImplemented from __add__. (Or A may not implement __add__ atall.)
  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 possiblemethods to try, so this is where the default implementationshould live.
  5. If B <: A, Python tries B.__radd__ before A.__add__. This isok, because it was implemented with knowledge of A, so it canhandle those instances before delegating to Complex.

IfA<:Complex andB<:Real without sharing any other knowledge,then the appropriate shared operation is the one involving the builtin complex, and both __radd__s land there, soa+b==b+a.

Rejected Alternatives

The initial version of this PEP defined an algebraic hierarchyinspired by a Haskell Numeric Prelude[1] includingMonoidUnderPlus, AdditiveGroup, Ring, and Field, and mentioned severalother possible algebraic types before getting to the numbers. We hadexpected this to be useful to people using vectors and matrices, butthe NumPy community really wasn’t interested, and we ran into theissue that even ifx is an instance ofX<:MonoidUnderPlusandy is an instance ofY<:MonoidUnderPlus,x+y maystill not make sense.

Then we gave the numbers a much more branching structure to includethings like the Gaussian Integers and Z/nZ, which could be Complex butwouldn’t necessarily support things like division. The communitydecided that this was too much complication for Python, so I’ve nowscaled back the proposal to resemble the Scheme numeric tower muchmore closely.

The Decimal Type

After consultation with its authors it has been decided that theDecimal type should not at this time be made part of the numerictower.

References

[1]
NumericPrelude: An experimental alternative hierarchyof numeric type classes(https://archives.haskell.org/code.haskell.org/numeric-prelude/docs/html/index.html)
[2]
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 thefirst place, to Travis Oliphant for pointing out that the numpy peopledidn’t really care about the algebraic concepts, to Alan Isaac forreminding me that Scheme had already done this, and to Guido vanRossum and lots of other people on the mailing list for refining theconcept.

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-3141.rst

Last modified:2025-01-30 01:21:51 GMT


[8]ページ先頭

©2009-2025 Movatter.jp