Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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。

  1. __trunc__(self),从新的内置函数 trunc(x) 调用,它返回在 0 和 x 之间最接近 x 的 Integral。
  2. __floor__(self),从 math.floor(x) 调用,它返回小于等于 x 的最大 Integral。
  3. __ceil__(self),从 math.ceil(x) 调用,它返回大于等于 x 的最小 Integral。
  4. __round__(self),从 round(x) 调用,它返回最接近 x 的 Integral,半值取整方式由类型选择。float 在 3.0 中将更改为向偶数舍入半值。还有一个两个参数的版本,__round__(self, ndigits),从 round(x, ndigits) 调用,它应该返回一个 Real。

在 2.6 中,math.floormath.ceilround 将继续返回浮点数。

float 实现的 int() 转换等同于 trunc()。一般来说,int() 转换应该首先尝试 __int__(),如果找不到,则尝试 __trunc__()

complex.__{divmod,mod,floordiv,int,float}__ 也将移除。如果能提供一个友好的错误消息来帮助困惑的移植者会很好,但更重要的是它们不出现在 help(complex) 中。

类型实现者须知

实现者应注意使相等的数字相等并将其哈希为相同的值。如果存在两个不同的实数扩展,这可能会很微妙。例如,复数类型可以合理地实现 hash() 如下

def __hash__(self):
    return hash(complex(self))

但应注意任何超出内置复数范围或精度值的情况。

添加更多数字抽象基类

当然,还有更多可能的数字 ABC,如果它排除了添加这些的可能性,那将是一个糟糕的层级结构。您可以使用以下方式在 ComplexReal 之间添加 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

  1. 如果 A 定义了接受 b 的 __add__,则一切正常。
  2. 如果 A 回退到样板代码,并且它从 __add__ 返回一个值,我们将错过 B 定义更智能的 __radd__ 的可能性,因此样板代码应该从 __add__ 返回 NotImplemented。(或者 A 可能根本没有实现 __add__。)
  3. 然后 B 的 __radd__ 有机会。如果它接受 a,则一切正常。
  4. 如果它回退到样板代码,就没有更多可能的方法可以尝试了,所以默认实现应该在这里。
  5. 如果 B <: A,Python 在 A.__add__ 之前尝试 B.__radd__。这是可以的,因为它是在了解 A 的情况下实现的,因此它可以在委托给 Complex 之前处理这些实例。

如果 A<:ComplexB<:Real 且没有共享任何其他知识,则适当的共享操作是涉及内置复数的操作,并且两个 __radd__s 都落在那里,因此 a+b == b+a

被拒绝的替代方案

本 PEP 的最初版本定义了一个受 Haskell Numeric Prelude [1] 启发的代数层级结构,包括 MonoidUnderPlus、AdditiveGroup、Ring 和 Field,并在涉及数字之前提到了其他几种可能的代数类型。我们曾期望这对于使用向量和矩阵的人会有用,但 NumPy 社区对此并不感兴趣,而且我们遇到了一个问题,即使 xX <: MonoidUnderPlus 的实例,yY <: 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

最后修改:2025-01-30 01:21:51 GMT