花下猫语:在 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 类开始,它是人们想象的数字类型的模糊概念。此类仅用于重载;它不提供任何操作。

  1. class Number(metaclass=ABCMeta): pass

大多数复数(complex number)的实现都是可散列的,但是如果你需要依赖它,则必须明确地检查:此层次结构支持可变的数。

  1. class Complex(Number):
  2. """Complex defines the operations that work on the builtin complex type.
  3. In short, those are: conversion to complex, bool(), .real, .imag,
  4. +, -, *, /, **, abs(), .conjugate(), ==, and !=.
  5. If it is given heterogenous arguments, and doesn't have special
  6. knowledge about them, it should fall back to the builtin complex
  7. type as described below.
  8. """
  9. @abstractmethod
  10. def __complex__(self):
  11. """Return a builtin complex instance."""
  12. def __bool__(self):
  13. """True if self != 0."""
  14. return self != 0
  15. @abstractproperty
  16. def real(self):
  17. """Retrieve the real component of this number.
  18. This should subclass Real.
  19. """
  20. raise NotImplementedError
  21. @abstractproperty
  22. def imag(self):
  23. """Retrieve the real component of this number.
  24. This should subclass Real.
  25. """
  26. raise NotImplementedError
  27. @abstractmethod
  28. def __add__(self, other):
  29. raise NotImplementedError
  30. @abstractmethod
  31. def __radd__(self, other):
  32. raise NotImplementedError
  33. @abstractmethod
  34. def __neg__(self):
  35. raise NotImplementedError
  36. def __pos__(self):
  37. """Coerces self to whatever class defines the method."""
  38. raise NotImplementedError
  39. def __sub__(self, other):
  40. return self + -other
  41. def __rsub__(self, other):
  42. return -self + other
  43. @abstractmethod
  44. def __mul__(self, other):
  45. raise NotImplementedError
  46. @abstractmethod
  47. def __rmul__(self, other):
  48. raise NotImplementedError
  49. @abstractmethod
  50. def __div__(self, other):
  51. """a/b; should promote to float or complex when necessary."""
  52. raise NotImplementedError
  53. @abstractmethod
  54. def __rdiv__(self, other):
  55. raise NotImplementedError
  56. @abstractmethod
  57. def __pow__(self, exponent):
  58. """a**b; should promote to float or complex when necessary."""
  59. raise NotImplementedError
  60. @abstractmethod
  61. def __rpow__(self, base):
  62. raise NotImplementedError
  63. @abstractmethod
  64. def __abs__(self):
  65. """Returns the Real distance from 0."""
  66. raise NotImplementedError
  67. @abstractmethod
  68. def conjugate(self):
  69. """(x+y*i).conjugate() returns (x-y*i)."""
  70. raise NotImplementedError
  71. @abstractmethod
  72. def __eq__(self, other):
  73. raise NotImplementedError
  74. # __ne__ is inherited from object and negates whatever __eq__ does.

Real抽象基类表示在实数轴上的值,并且支持内置的float的操作。实数(Real number)是完全有序的,除了 NaN(本 PEP 基本上不考虑它)。

  1. class Real(Complex):
  2. """To Complex, Real adds the operations that work on real numbers.
  3. In short, those are: conversion to float, trunc(), math.floor(),
  4. math.ceil(), round(), divmod(), //, %, <, <=, >, and >=.
  5. Real also provides defaults for some of the derived operations.
  6. """
  7. # XXX What to do about the __int__ implementation that's
  8. # currently present on float? Get rid of it?
  9. @abstractmethod
  10. def __float__(self):
  11. """Any Real can be converted to a native float object."""
  12. raise NotImplementedError
  13. @abstractmethod
  14. def __trunc__(self):
  15. """Truncates self to an Integral.
  16. Returns an Integral i such that:
  17. * i>=0 iff self>0;
  18. * abs(i) <= abs(self);
  19. * for any Integral j satisfying the first two conditions,
  20. abs(i) >= abs(j) [i.e. i has "maximal" abs among those].
  21. i.e. "truncate towards 0".
  22. """
  23. raise NotImplementedError
  24. @abstractmethod
  25. def __floor__(self):
  26. """Finds the greatest Integral <= self."""
  27. raise NotImplementedError
  28. @abstractmethod
  29. def __ceil__(self):
  30. """Finds the least Integral >= self."""
  31. raise NotImplementedError
  32. @abstractmethod
  33. def __round__(self, ndigits:Integral=None):
  34. """Rounds self to ndigits decimal places, defaulting to 0.
  35. If ndigits is omitted or None, returns an Integral,
  36. otherwise returns a Real, preferably of the same type as
  37. self. Types may choose which direction to round half. For
  38. example, float rounds half toward even.
  39. """
  40. raise NotImplementedError
  41. def __divmod__(self, other):
  42. """The pair (self // other, self % other).
  43. Sometimes this can be computed faster than the pair of
  44. operations.
  45. """
  46. return (self // other, self % other)
  47. def __rdivmod__(self, other):
  48. """The pair (self // other, self % other).
  49. Sometimes this can be computed faster than the pair of
  50. operations.
  51. """
  52. return (other // self, other % self)
  53. @abstractmethod
  54. def __floordiv__(self, other):
  55. """The floor() of self/other. Integral."""
  56. raise NotImplementedError
  57. @abstractmethod
  58. def __rfloordiv__(self, other):
  59. """The floor() of other/self."""
  60. raise NotImplementedError
  61. @abstractmethod
  62. def __mod__(self, other):
  63. """self % other
  64. See
  65. https://mail.python.org/pipermail/python-3000/2006-May/001735.html
  66. and consider using "self/other - trunc(self/other)"
  67. instead if you're worried about round-off errors.
  68. """
  69. raise NotImplementedError
  70. @abstractmethod
  71. def __rmod__(self, other):
  72. """other % self"""
  73. raise NotImplementedError
  74. @abstractmethod
  75. def __lt__(self, other):
  76. """< on Reals defines a total ordering, except perhaps for NaN."""
  77. raise NotImplementedError
  78. @abstractmethod
  79. def __le__(self, other):
  80. raise NotImplementedError
  81. # __gt__ and __ge__ are automatically done by reversing the arguments.
  82. # (But __le__ is not computed as the opposite of __gt__!)
  83. # Concrete implementations of Complex abstract methods.
  84. # Subclasses may override these, but don't have to.
  85. def __complex__(self):
  86. return complex(float(self))
  87. @property
  88. def real(self):
  89. return +self
  90. @property
  91. def imag(self):
  92. return 0
  93. def conjugate(self):
  94. """Conjugate is a no-op for Reals."""
  95. return +self

我们应该整理 Demo/classes/Rat.py,并把它提升为 Rational.py 加入标准库。然后它将实现有理数(Rational)抽象基类。

  1. class Rational(Real, Exact):
  2. """.numerator and .denominator should be in lowest terms."""
  3. @abstractproperty
  4. def numerator(self):
  5. raise NotImplementedError
  6. @abstractproperty
  7. def denominator(self):
  8. raise NotImplementedError
  9. # Concrete implementation of Real's conversion to float.
  10. # (This invokes Integer.__div__().)
  11. def __float__(self):
  12. return self.numerator / self.denominator

最后是整数类:

  1. class Integral(Rational):
  2. """Integral adds a conversion to int and the bit-string operations."""
  3. @abstractmethod
  4. def __int__(self):
  5. raise NotImplementedError
  6. def __index__(self):
  7. """__index__() exists because float has __int__()."""
  8. return int(self)
  9. def __lshift__(self, other):
  10. return int(self) << int(other)
  11. def __rlshift__(self, other):
  12. return int(other) << int(self)
  13. def __rshift__(self, other):
  14. return int(self) >> int(other)
  15. def __rrshift__(self, other):
  16. return int(other) >> int(self)
  17. def __and__(self, other):
  18. return int(self) & int(other)
  19. def __rand__(self, other):
  20. return int(other) & int(self)
  21. def __xor__(self, other):
  22. return int(self) ^ int(other)
  23. def __rxor__(self, other):
  24. return int(other) ^ int(self)
  25. def __or__(self, other):
  26. return int(self) | int(other)
  27. def __ror__(self, other):
  28. return int(other) | int(self)
  29. def __invert__(self):
  30. return ~int(self)
  31. # Concrete implementations of Rational and Real abstract methods.
  32. def __float__(self):
  33. """float(self) == float(int(self))"""
  34. return float(int(self))
  35. @property
  36. def numerator(self):
  37. """Integers are their own numerators."""
  38. return +self
  39. @property
  40. def denominator(self):
  41. """Integers have a denominator of 1."""
  42. return 1

运算及__magic__方法的变更

为了支持从 float 到 int(确切地说,从 Real 到 Integral)的精度收缩,我们提出了以下新的 __magic__ 方法,可以从相应的库函数中调用。所有这些方法都返回 Intergral 而不是 Real。

  1. __trunc__(self):在新的内置 trunc(x) 里调用,它返回从 0 到 x 之间的最接近 x 的 Integral。
  2. __floor__(self):在 math.floor(x) 里调用,返回最大的 Integral <= x。
  3. __ceil__(self):在 math.ceil(x) 里调用,返回最小的 Integral > = x。
  4. __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():

  1. def __hash__(self):
  2. return hash(complex(self))

但应注意所有超出了内置复数范围或精度的值。

添加更多数字抽象基类

当然,数字还可能有更多的抽象基类,如果排除了添加这些数字的可能性,这会是一个糟糕的等级体系。你可以使用以下方法在 Complex 和 Real 之间添加MyFoo:

  1. class MyFoo(Complex): ...
  2. MyFoo.register(Real)

实现算术运算

我们希望实现算术运算,使得在混合模式的运算时,要么调用者知道如何处理两种参数类型,要么将两者都转换为最接近的内置类型,并以此进行操作。

对于 Integral 的子类型,这意味着__add__和__radd__应该被定义为:

  1. class MyIntegral(Integral):
  2. def __add__(self, other):
  3. if isinstance(other, MyIntegral):
  4. return do_my_adding_stuff(self, other)
  5. elif isinstance(other, OtherTypeIKnowAbout):
  6. return do_my_other_adding_stuff(self, other)
  7. else:
  8. return NotImplemented
  9. def __radd__(self, other):
  10. if isinstance(other, MyIntegral):
  11. return do_my_adding_stuff(other, self)
  12. elif isinstance(other, OtherTypeIKnowAbout):
  13. return do_my_other_adding_stuff(other, self)
  14. elif isinstance(other, Integral):
  15. return int(other) + int(self)
  16. elif isinstance(other, Real):
  17. return float(other) + float(self)
  18. elif isinstance(other, Complex):
  19. return complex(other) + complex(self)
  20. else:
  21. return NotImplemented

对 Complex 的子类进行混合类型操作有 5 种不同的情况。我把以上所有未包含 MyIntegral 和 OtherTypeIKnowAbout 的代码称为“样板”。

a 是 A 的实例,它是Complex(a : A <: Complex) 的子类型,还有 b : B <: Complex。对于 a + b,我这么考虑:

  1. 如果 A 定义了接受 b 的__add__,那么没问题。
  2. 如果 A 走到了样板代码分支(译注:else 分支),还从__add__返回一个值的话,那么我们就错过了为 B 定义一个更智能的__radd__的可能性,因此样板应该从__add__返回 NotImplemented。(或者 A 可以不实现__add__)
  3. 然后 B 的__radd__的机会来了。如果它接受 a,那么没问题。
  4. 如果它走到样板分支上,就没有办法了,因此需要有默认的实现。
  5. 如果 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

Python 中的数字到底是什么?的更多相关文章

  1. 8.python中的数字

    python中数字对象的创建如下, a = 123 b = 1.23 c = 1+1j 可以直接输入数字,然后赋值给变量. 同样也可是使用类的方式: a = int(123) b = float(1. ...

  2. 2、python中的数字

    第二篇开始谈谈python中的数据. 一.前言 python中的数字包含了整数.浮点数.复数三种.在python的早期版本,或许可以看到正数被分为长整数与短整数,后来被取消了,因此这里不作讨论.通常我 ...

  3. python中的cls到底指的是什么

    python中的cls到底指的是什么,与self有什么区别? 2018年07月31日 11:13:09 rs勿忘初心 阅读数:7769   作者:秦风链接:https://www.zhihu.com/ ...

  4. python中的数字取整(ceil,floor,round)概念和用法

    python中的数学运算函数(ceil,floor,round)的主要任务是截掉小数以后的位数.总体来说 就是取整用的.只是三者之间有微妙的区别:   floor() :把数字变小 ceil() : ...

  5. python中的数字

    在编程中,通常使用数字来记录游戏得分,表示可视化数据.存储web应用信息等. #运算# 1,基本运算 >>> 2+35>>> 1-2-1>>> 3 ...

  6. python中,数字类型计算

    说明: 今天在看python数字类型的操作,在此记录下. 操作过程: 1.数字的加减乘除 >>> 2 + 24>>> 4 - 22>>> 2 - ...

  7. Python中 各种数字类型的判别(numerica, digital, decimal)

    一. 全角和半角 全角:是指一个全角字符占用两个标准字符(或两个半角字符)的位置. 全角占两个字节. 汉字字符和规定了全角的英文字符及国标GB2312-80中的图形符号和特殊字符都是全角字符.在全角中 ...

  8. python学习之【第二篇】:Python中的数字及其所具有的方法

    1.前言 Python 数字(number)数据类型用于存储数值.数据类型是不允许改变的,这就意味着如果改变数字数据类型的值,将重新分配内存空间. 2.创建数字对象 以下实例在变量赋值时 Number ...

  9. python中 将数字转化为人民币的形式

    def fn(args): """ 将金额转化为人民币模式,带逗号分隔,保留小数点两位,四舍五入 :param args: :return: ""&q ...

随机推荐

  1. ipa包如何打包?ios打包ipa的四种方法分享

      今天带来的内容是ios打包ipa的四种方法.总结一下,目前.app包转为.ipa包的方法有以下几种,下面一起来看看吧!    1.Apple推荐的方式,即实用xcode的archive功能 Xco ...

  2. 双下划线开头的attr方法

    # class Foo: # x=1 # def __init__(self,y): # self.y=y # # def __getattr__(self, item): # print('执行__ ...

  3. 《Python测试开发技术栈—巴哥职场进化记》—软件测试工程师“兵器库”

    上文<Python测试开发技术栈-巴哥职场进化记>-初来乍到,请多关照 我们介绍了巴哥入职后见到了自己的导师华哥,第一次参加团队站会,认识了团队中的开发小哥哥和产品小姐姐以及吃到了公司的加 ...

  4. 2020 Multi-University Training Contest 1 部分题解

    目录 Cookies Distinct Sub-palindromes Fibonacci Sum Finding a MEX Leading Robots Math is Simple Minimu ...

  5. java BigInteger与BigDecimal

    一 BigInteger java中long型为最大整数类型,对于超过long型的数据如何去表示呢.在Java的世界中,超过long型 的整数已经不能被称为整数了,它们被封装成BigInteger对象 ...

  6. 2020-06-25:B+树和B树有什么区别?

    福哥答案2020-06-25: B树:1.叶子节点和非叶子节点都存数据.2.数据无链指针.B+树:1.只有叶子节点存数据.2.数据有链指针.B树优势:1.靠近根节点的数据,访问速度快.B+树优势:1. ...

  7. 怎么把txt转换成excel

    地址: https://jingyan.baidu.com/article/c1465413b2f2c50bfdfc4c61.html

  8. yum安装软件时,提示No package netstat available.的解决方法

    1. 序言 如笔者在本机上运行netstat时,提示没有这个命令,向来简单粗暴,直接yum -y install netstat,显然是不能正常安装的. [root@hadoop-103 ~]# yu ...

  9. cinder api启动过程源码分析

    1.启动cinder-api服务 当你通过cinder-api命令(如:/usr/bin/cinder-api --config-file /etc/cinder/cinder.conf)启动api服 ...

  10. Linux环境下安装MySQL数据库

    Linux安装mysql服务分两种安装方法: (1).源码安装,优点是安装包比较小,只有十多M,缺点是安装依赖的库多,安装编译时间长,安装步骤复杂容易出错: (2).使用官方编译好的二进制文件安装,优 ...