Python 中的数字到底是什么?
花下猫语:在 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/chinesehuazhou/peps-cn
概要
本提案定义了一种抽象基类(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 类开始,它是人们想象的数字类型的模糊概念。此类仅用于重载;它不提供任何操作。
大多数复数(complex number)的实现都是可散列的,但是如果你需要依赖它,则必须明确地检查:此层次结构支持可变的数。
Real
抽象基类表示在实数轴上的值,并且支持内置的float
的操作。实数(Real number)是完全有序的,除了 NaN(本 PEP 基本上不考虑它)。
我们应该整理 Demo/classes/Rat.py,并把它提升为 Rational.py 加入标准库。然后它将实现有理数(Rational)抽象基类。
最后是整数类:
运算及\\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():
但应注意所有超出了内置复数范围或精度的值。
添加更多数字抽象基类
当然,数字还可能有更多的抽象基类,如果排除了添加这些数字的可能性,这会是一个糟糕的等级体系。你可以使用以下方法在 Complex 和 Real 之间添加MyFoo:
实现算术运算
我们希望实现算术运算,使得在混合模式的运算时,要么调用者知道如何处理两种参数类型,要么将两者都转换为最接近的内置类型,并以此进行操作。
对于 Integral 的子类型,这意味着\\add\_\_和\\radd\\应该被定义为:
对 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中文翻译计划”已收录过一篇[译文](https://www.cnblogs.com/popapa/p/PEP3141.html),有些地方译得不尽相同,读者们可比对阅读。)
致谢
感谢 Neal Norwitz 最初鼓励我编写此 PEP,感谢 Travis Oliphant 指出 numpy 社区并不真正关心代数概念,感谢 Alan Isaac 提醒我 Scheme 已经做到了,以及感谢 Guido van Rossum 和邮件组里的其他人帮忙完善了这套概念。
版权
该文档已放入公共领域。
源文件:https://github.com/python/peps/blob/master/pep-3141.txt
版权声明: 本文为 InfoQ 作者【Python猫】的原创文章。
原文链接:【http://xie.infoq.cn/article/5207b0d2f8b0fe53bfc5fd791】。文章转载请联系作者。
评论