花下猫语:在 Python 中,不同类型的数字能够间接做算术运算,并不需要作显式的类型转换。然而,它的“隐式类型转换”可能跟其它语言不同,因为 Python 中的数字是一种非凡的对象,派生自同一个形象基类。在上一篇文章 中,咱们探讨到了 Python 数字的运算,而后我想探索“Python 的数字对象到底是什么”的话题,所以就翻译了这篇 PEP,心愿对你也有所帮忙。
PEP原文: https://www.python.org/dev/peps/pep-3141/
PEP题目: PEP 3141 — A Type Hierarchy for Numbers
PEP作者: Jeffrey Yasskin
创立日期: 2007-04-23
译者 :豌豆花下猫@Python猫公众号
PEP翻译打算: https://github.com/chinesehua…
概要
本提案定义了一种形象基类(ABC)(PEP 3119)的层次结构,用来示意相似数字(number-like)的类。它提出了一个 Number :> Complex :> Real :> Rational :> Integral 的层次结构,其中 A :> B 示意“A 是 B 的超类”。该层次结构受到了 Scheme 的数字塔(numeric tower)启发。(译注:数字–复数–实数–有理数–整数)
基本原理
以数字作为参数的函数应该可能断定这些数字的属性,并且依据数字的类型,确定是否以及何时进行重载,即基于参数的类型,函数应该是可重载的。
例如,切片要求其参数为Integrals
,而math
模块中的函数要求其参数为Real
。
标准
本 PEP 规定了一组形象基类(Abstract Base Class),并提出了一个实现某些办法的通用策略。它应用了来自于PEP 3119的术语,然而该层次结构旨在对特定类集的任何零碎办法都有意义。
规范库中的类型查看应该应用这些类,而不是具体的内置类型。
数值类
咱们从 Number 类开始,它是人们设想的数字类型的含糊概念。此类仅用于重载;它不提供任何操作。
class Number(metaclass=ABCMeta): pass
大多数复数(complex number)的实现都是可散列的,然而如果你须要依赖它,则必须明确地查看:此层次结构反对可变的数。
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.""" 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.
Real
形象基类示意在实数轴上的值,并且反对内置的float
的操作。实数(Real number)是齐全有序的,除了 NaN(本 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
咱们应该整顿 Demo/classes/Rat.py,并把它晋升为 Rational.py 退出规范库。而后它将实现有理数(Rational)形象基类。
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
最初是整数类:
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
运算及__magic__办法的变更
为了反对从 float 到 int(确切地说,从 Real 到 Integral)的精度膨胀,咱们提出了以下新的 __magic__ 办法,能够从相应的库函数中调用。所有这些办法都返回 Intergral 而不是 Real。
- __trunc__(self):在新的内置 trunc(x) 里调用,它返回从 0 到 x 之间的最靠近 x 的 Integral。
- __floor__(self):在 math.floor(x) 里调用,返回最大的 Integral <= x。
- __ceil__(self):在 math.ceil(x) 里调用,返回最小的 Integral > = x。
- __round__(self):在 round(x) 里调用,返回最靠近 x 的 Integral ,依据选定的类型作四舍五入。浮点数将从 3.0 版本起改为向偶数端四舍五入。(译注:round(2.5) 等于 2,round(3.5) 等于 4)。它还有一个带两参数的版本__round__(self, ndigits),被 round(x, ndigits) 调用,但返回的是一个 Real。
在 2.6 版本中,math.floor、math.ceil 和 round 将持续返回浮点数。
float 的 int() 转换等效于 trunc()。一般而言,int() 的转换首先会尝试__int__(),如果找不到,再尝试__trunc__()。
complex.__{divmod, mod, floordiv, int, float}__ 也隐没了。提供一个好的谬误音讯来帮忙困惑的搬运工会很好,但更重要的是不呈现在 help(complex) 中。
给类型实现者的阐明
实现者应该留神使相等的数字相等,并将它们散列为雷同的值。如果实数有两个不同的扩大,这可能会变得奥妙。例如,一个复数类型能够像这样正当地实现 hash():
def __hash__(self): return hash(complex(self))
但应留神所有超出了内置复数范畴或精度的值。
增加更多数字形象基类
当然,数字还可能有更多的形象基类,如果排除了增加这些数字的可能性,这会是一个蹩脚的等级体系。你能够应用以下办法在 Complex 和 Real 之间增加MyFoo:
class MyFoo(Complex): ... MyFoo.register(Real)
实现算术运算
咱们心愿实现算术运算,使得在混合模式的运算时,要么调用者晓得如何解决两种参数类型,要么将两者都转换为最靠近的内置类型,并以此进行操作。
对于 Integral 的子类型,这意味着__add__和__radd__应该被定义为:
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
对 Complex 的子类进行混合类型操作有 5 种不同的状况。我把以上所有未蕴含 MyIntegral 和 OtherTypeIKnowAbout 的代码称为“样板”。
a 是 A 的实例,它是Complex(a : A <: Complex)
的子类型,还有 b : B <: Complex
。对于 a + b,我这么思考:
- 如果 A 定义了承受 b 的__add__,那么没问题。
- 如果 A 走到了样板代码分支(译注:else 分支),还从__add__返回一个值的话,那么咱们就错过了为 B 定义一个更智能的__radd__的可能性,因而样板应该从__add__返回 NotImplemented。(或者 A 能够不实现__add__)
- 而后 B 的__radd__的机会来了。如果它承受 a,那么没问题。
- 如果它走到样板分支上,就没有方法了,因而须要有默认的实现。
- 如果 B <: A,则 Python 会在 A.__ add__之前尝试 B.__ radd__。这也能够,因为它是基于 A 而实现的,因而能够在委派给 Complex 之前解决这些实例。
如果 A <: Complex 和 B <: Real 没有其它关系,则适合的共享操作是内置复数的操作,它们的__radd__都在其中,因而 a + b == b + a。(译注:这几段没看太明确,可能译得不对)
被回绝的计划
本 PEP 的初始版本定义了一个被 Haskell Numeric Prelude 所启发的代数层次结构,其中包含 MonoidUnderPlus、AdditiveGroup、Ring 和 Field,并在失去数字之前,还有其它几种可能的代数类型。
咱们本来心愿这对应用向量和矩阵的人有用,但 NumPy 社区的确对此并不感兴趣,另外咱们还遇到了一个问题,即使 x 是 X <: MonoidUnderPlus 的实例,而且 y 是 Y < : MonoidUnderPlus 的实例,x + y 可能还是行不通。
而后,咱们为数字提供了更多的分支构造,包含高斯整数(Gaussian Integer)和 Z/nZ 之类的货色,它们能够是 Complex,但不肯定反对“除”之类的操作。
社区认为这对 Python 来说太简单了,因而我当初放大了提案的范畴,使其更靠近于 Scheme 数字塔。
十进制类型
经与作者协商,已决定目前不将 Decimal 类型作为数字塔的一部分。
参考文献
1、形象基类简介:http://www.python.org/dev/peps/pep-3119/
2、可能是 Python 3 的类树?Bill Janssen 的 Wiki 页面:http://wiki.python.org/moin/AbstractBaseClasses
3、NumericPrelude:数字类型类的实验性备选层次结构:http://darcs.haskell.org/numericprelude/docs/html/index.html
4、Scheme 数字塔:https://groups.csail.mit.edu/mac/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50
(译注:在译完之后,我才发现“PEP中文翻译打算”已收录过一篇译文,有些中央译得不尽相同,读者们可比对浏览。)
致谢
感激 Neal Norwitz 最后激励我编写此 PEP,感激 Travis Oliphant 指出 numpy 社区并不真正关怀代数概念,感激 Alan Isaac 揭示我 Scheme 曾经做到了,以及感激 Guido van Rossum 和邮件组里的其他人帮忙欠缺了这套概念。
版权
该文档已放入公共畛域。
源文件:https://github.com/python/peps/blob/master/pep-3141.txt