In Python, numbers of different types can be computed directly without explicit type conversions. However, its “implicit conversion” may differ from other languages because numbers in Python are a special kind of object derived from the same abstract base class. In the last article, we talked about Python number operations, and I wanted to explore the topic of “What exactly are Python number objects”, so I translated this PEP in the hope that it would help you as well.
The original PEP: www.python.org/dev/peps/pe…
PEP title: PEP 3141 — A Type Hierarchy for Numbers
PEP作者: Jeffrey Yasskin
Created on: 2007-04-23
Translator: Pea flower under the cat @python cat public account
PEP Translation Program: github.com/chinesehuaz…
The profile
This proposal defines a hierarchy of abstract base classes (ABC) (PEP 3119) to represent number-like classes. It proposes A hierarchy of Number :> Complex :> Real :> Rational :> Integral, where A :> B means “A is the superclass of B.” The hierarchy is inspired by Scheme’s Numeric Tower. (Numbers, real numbers, whole numbers)
The basic principle of
Functions that take numbers as arguments should be able to determine the attributes of those numbers and, depending on the type of the number, determine if and when to overload, that is, functions should be overloaded based on the type of the argument.
For example, slicing requires its argument to be Integrals, while functions in the Math module require their argument to be Real.
specification
This PEP specifies a set of Abstract Base classes and proposes a common strategy for implementing certain methods. It uses terminology from PEP 3119, but the hierarchy is intended to make sense to any system method for a particular set of classes.
Type checking in the standard library should use these classes, not specific built-in types.
Numerical class
We start with the Number class, which is a vague notion of what people imagine a Number type to be. This class is only used for overloading; It does not provide any operations.
class Number(metaclass=ABCMeta) : pass
Copy the code
Most implementations of complex numbers are hashed, but if you need to rely on it, you must explicitly check that the hierarchy supports mutable numbers.
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 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."""
def __bool__(self) :
"""True if self ! = 0. ""
returnself ! =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.
Copy the code
The Real abstract base class represents values on the Real number line and supports built-in float operations. Real numbers are perfectly ordered, except for NaN (which is largely ignored in this PEP).
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
Copy the code
We should clean up Demo/classes/ rat. py and promote it to Rational. Py and add it to the standard library. It then implements the Rational abstract base class.
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
Copy the code
Finally, the integer class:
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
Copy the code
Operations and changes to the __magic__ method
To support precision shrinkage from float to int (or, more specifically, Real to Integral), we propose the following new __magic__ methods, which can be called from the corresponding library function. All of these methods return Intergral instead of Real.
- __trunc__(self) : Called in the new built-in trunc(x), which returns the closest Integral to x between 0 and x.
- __floor__(self) : called in math.floor(x), returns the maximum Integral <= x.
- __ceil__(self) : called in math.ceil(x), returns the smallest Integral > = x.
- __round__(self) : Called in round(x), returns the Integral closest to x, rounded according to the selected type. Floating point numbers will be rounded to even numbers as of version 3.0. Round (2.5 = 2, 3.5 = 4). It also has a two-argument version of __round__(self, ndigits), called by round(x, ndigits), but returns a Real.
In version 2.6, math.floor, math.ceil, and round will continue to return floating point numbers.
The int() conversion of float is equivalent to trunc(). In general, int() conversions first try __int__(), and then __trunc__() if not found.
Complex.__{divmod, mod, floordiv, int, float}__ also disappeared. Providing a good error message to help confused movers is great, but more importantly not in Help (Complex).
Description to type implementers
Implementors should take care to make equal numbers equal and hash them to the same value. This can get tricky if the real numbers have two different extensions. For example, a complex type might reasonably implement hash() like this:
def __hash__(self) :
return hash(complex(self))
Copy the code
Note, however, any values that are outside the range or precision of the built-in complex numbers.
Add more numeric abstract base classes
Of course, numbers can also have more abstract base classes, which would be a bad hierarchy if you excluded the possibility of adding these numbers. You can add MyFoo between Complex and Real using the following method:
class MyFoo(Complex) :. MyFoo.register(Real)Copy the code
Implement arithmetic
We want to implement arithmetic operations such that in mixed-mode operations either the caller knows how to handle both parameter types or converts both to the closest built-in type and operates accordingly.
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
Copy the code
There are five different scenarios for mixed-type operations on subclasses of Complex. I call all of the above code that does not include MyIntegral and OtherTypeIKnowAbout “boilerplate.”
A is an instance of A, which is a subtype of Complex(a: a <: Complex), and b: b <: Complex. So for a plus b, let me think of it this way:
- If A defines an __add__ that accepts B, then no problem.
- If A goes to the boilerplate branch and returns A value from __add__, then we miss the possibility of defining A smarter __radd__ for B, so the boilerplate should return NotImplemented from __add__. (Or A may not implement __add__)
- Then comes the opportunity for B’s __radd__. If it accepts a, then no problem.
- If it goes to the boilerplate branch, there’s no way around it, so you need a default implementation.
- If B <: A, Python tries B.__ radd__ before A.__ add__. This is also ok, because it is implemented based on A, so you can process these instances before delegating to Complex.
If A <: Complex and B <: Real have no other relationship, then the appropriate shared operation is the one with the built-in Complex numbers, and their __radd__ is in it, so A + B == B + A. (This paragraph is not clear.)
The rejected proposal
The initial version of this PEP defines an algebraic hierarchy inspired by Haskell Numeric Prelude, which includes MonoidUnderPlus, AdditiveGroup, Ring, and Field, and before getting to the numbers, There are several other possible algebraic types.
We had hoped this would be useful for people who work with vectors and matrices, but the NumPy community really wasn’t interested, and we ran into a problem even though x was an instance of x <: MonoidUnderPlus and y was y <: For MonoidUnderPlus instances, x + y may still not work.
We then provided more branching structures for numbers, including things like Gaussian Integer and Z/nZ, which can be Complex but don’t necessarily support operations like “divide.”
The community felt that this was too complex for Python, so I have now narrowed down the proposal to more closely resemble Scheme digital towers.
Decimal type
In consultation with the authors, it has been decided not to include the Decimal type as part of the digital tower at this time.
reference
1, the abstract base class description: www.python.org/dev/peps/pe…
2. Possibly a Python 3 class tree? Bill Janssen Wiki page: wiki.python.org/moin/Abstra…
3, NumericPrelude: experimental class numeric types alternate hierarchy: darcs.haskell.org/numericprel…
4, a Scheme of digital tower: groups.csail.mit.edu/mac/ftpdir/…
(Only later did I find out that PEP’s Chinese Translation Project included a translation, with some variations that make it easier for readers to read.)
Thank you
Thanks to Neal Norwitz for encouraging me to write this PEP in the first place, thanks to Travis Oliphant for pointing out that the Numpy community doesn’t really care about algebraic concepts, thanks to Alan Isaac for reminding me that Scheme already does, And thanks to Guido Van Rossum and others in the mailing list for helping to refine the concept.
copyright
The document is in the public domain.
Source file: github.com/python/peps…