PEP 3141 – 数字类型层级结构
- 作者:
- Jeffrey Yasskin <jyasskin at google.com>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2007年4月23日
- Python 版本:
- 3.0
- 发布历史:
- 2007年4月25日,2007年5月16日,2007年8月2日
摘要
本提案定义了一个抽象基类(ABCs)的层级结构(PEP 3119),用于表示类似数字的类。它提出了一个 Number :> Complex :> Real :> Rational :> Integral 的层级结构,其中 A :> B 表示“A是B的超类型”。该层级结构受 Scheme 的数字塔 [2] 启发。
基本原理
接受数字作为参数的函数应该能够确定这些数字的属性,并且如果以及何时在语言中添加基于类型的重载,应该能够根据参数类型进行重载。例如,切片要求其参数为 Integrals,而 math 模块中的函数要求其参数为 Real。
规范
本 PEP 规定了一组抽象基类,并提出了实现某些方法的通用策略。它使用了 PEP 3119 中的术语,但该层级结构旨在对任何系统性的类定义方法都具有意义。
标准库中的类型检查应该使用这些类而不是具体的内置类型。
数字类
我们从一个 Number 类开始,以便人们更容易模糊地表达他们期望的数字类型。这个类只对重载有帮助;它不提供任何操作。
class Number(metaclass=ABCMeta): pass
大多数复数实现将是可哈希的,但如果您需要依赖此功能,则必须显式检查:此层级结构支持可变数字。
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 heterogeneous 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 imaginary 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 ABC 表示该值位于实数线上,并支持 float 内置类型的功能。实数是全序的,除了 NaN(本 PEP 基本忽略了 NaN)。
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 ABC。
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__ 方法,这些方法将从相应的库函数中调用。所有这些方法都返回 Integrals 而不是 Reals。
__trunc__(self),从新的内置函数trunc(x)调用,它返回在 0 和x之间最接近x的 Integral。__floor__(self),从math.floor(x)调用,它返回小于等于x的最大 Integral。__ceil__(self),从math.ceil(x)调用,它返回大于等于x的最小 Integral。__round__(self),从round(x)调用,它返回最接近x的 Integral,半值取整方式由类型选择。float在 3.0 中将更改为向偶数舍入半值。还有一个两个参数的版本,__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))
但应注意任何超出内置复数范围或精度值的情况。
添加更多数字抽象基类
当然,还有更多可能的数字 ABC,如果它排除了添加这些的可能性,那将是一个糟糕的层级结构。您可以使用以下方式在 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 回退到样板代码,并且它从 __add__ 返回一个值,我们将错过 B 定义更智能的 __radd__ 的可能性,因此样板代码应该从 __add__ 返回 NotImplemented。(或者 A 可能根本没有实现 __add__。)
- 然后 B 的 __radd__ 有机会。如果它接受 a,则一切正常。
- 如果它回退到样板代码,就没有更多可能的方法可以尝试了,所以默认实现应该在这里。
- 如果 B <: A,Python 在 A.__add__ 之前尝试 B.__radd__。这是可以的,因为它是在了解 A 的情况下实现的,因此它可以在委托给 Complex 之前处理这些实例。
如果 A<:Complex 且 B<:Real 且没有共享任何其他知识,则适当的共享操作是涉及内置复数的操作,并且两个 __radd__s 都落在那里,因此 a+b == b+a。
被拒绝的替代方案
本 PEP 的最初版本定义了一个受 Haskell Numeric Prelude [1] 启发的代数层级结构,包括 MonoidUnderPlus、AdditiveGroup、Ring 和 Field,并在涉及数字之前提到了其他几种可能的代数类型。我们曾期望这对于使用向量和矩阵的人会有用,但 NumPy 社区对此并不感兴趣,而且我们遇到了一个问题,即使 x 是 X <: MonoidUnderPlus 的实例,y 是 Y <: MonoidUnderPlus 的实例,x + y 仍然可能没有意义。
然后,我们为数字设计了一个更具分支的结构,以包括高斯整数和 Z/nZ 等,它们可以是 Complex 但不一定支持除法等操作。社区认为这对于 Python 来说过于复杂,所以我现在已经将提案缩减为更接近 Scheme 数字塔的结构。
Decimal 类型
经与作者协商,已决定 Decimal 类型目前不应成为数字塔的一部分。
参考资料
- 可能的 Python 3K 类树?Bill Janssen 的 wiki 页面
- (https://wiki.python.org/moin/AbstractBaseClasses)
致谢
感谢 Neal Norwitz 鼓励我撰写这篇 PEP,感谢 Travis Oliphant 指出 numpy 人员并不真正关心代数概念,感谢 Alan Isaac 提醒我 Scheme 已经做过这些,以及 Guido van Rossum 和邮件列表上的许多其他人对概念的完善。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3141.rst