PEP 3141 – 数字类型层次结构
- 作者:
- Jeffrey Yasskin <jyasskin at google.com>
- 状态:
- 最终
- 类型:
- 标准轨迹
- 创建:
- 2007-04-23
- Python 版本:
- 3.0
- 历史记录:
- 2007-04-25, 2007-05-16, 2007-08-02
摘要
本提案定义了用于表示类似数字的类的抽象基类 (ABC) (PEP 3119) 的层次结构。它提出了一个 Number :> Complex :> Real :> Rational :> Integral
层次结构,其中 A :> B
表示“A 是 B 的超类型”。该层次结构的灵感来自 Scheme 的数字塔 [3]。
基本原理
接受数字作为参数的函数应该能够确定这些数字的属性,并且如果以及何时将基于类型的重载添加到语言中,应该可以根据参数的类型进行重载。例如,切片要求其参数为 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.
The Real
ABC 指示该值位于实线上,并支持 float
内置类的操作。实数是完全有序的,除了 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 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)
调用,该函数返回最接近x
的 Integral,介于 0 和x
之间。__floor__(self)
,从math.floor(x)
调用,该函数返回最大的 Integral<= x
。__ceil__(self)
,从math.ceil(x)
调用,该函数返回最小的 Integral>= x
。__round__(self)
,从round(x)
调用,该函数返回最接近x
的 Integral,四舍五入到类型选择的半边。float
将在 3.0 中更改为四舍五入到偶数。还有一个 2 个参数的版本,__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
当然,还有更多可能的数字 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__ 都落在了那里,因此 a+b == b+a
。
被拒绝的方案
本 PEP 的初始版本定义了一个受 Haskell 数值前缀 [2] 启发的代数层次结构,包括 MonoidUnderPlus、AdditiveGroup、Ring 和 Field,并在进入数字之前提到了几个其他可能的代数类型。我们预计这对使用向量和矩阵的人有用,但 NumPy 社区对此并不感兴趣,我们遇到了一个问题,即使 x
是 X <: MonoidUnderPlus
的实例,并且 y
是 Y <: MonoidUnderPlus
的实例,x + y
可能仍然没有意义。
然后我们给数字一个更分支的结构,以包括高斯整数和 Z/nZ 等内容,它们可能是 Complex,但不会一定支持除法。社区决定,这对于 Python 来说过于复杂,因此我现在已将提案缩减为更类似于 Scheme 数值塔。
Decimal 类型
在与作者协商后,已决定目前不将 Decimal
类型作为数值塔的一部分。
参考文献
致谢
感谢 Neal Norwitz 鼓励我最初编写本 PEP,感谢 Travis Oliphant 指出 numpy 人们并不关心代数概念,感谢 Alan Isaac 提醒我 Scheme 已经做到了这一点,感谢 Guido van Rossum 以及邮件列表中的许多其他人完善了这个概念。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3141.rst