PEP 3141 數值類型的層次結構 -- Python官方文檔譯文 [原創]


PEP 3141 -- 數值類型的層次結構(A Type Hierarchy for Numbers)

英文原文:https://www.python.org/dev/peps/pep-3141
采集日期:2020-02-27

PEP: 3141
Title: A Type Hierarchy for Numbers
Author: Jeffrey Yasskin jyasskin@google.com
Status: Final
Type: Standards Track
Created: 23-Apr-2007
Post-History: 25-Apr-2007, 16-May-2007, 02-Aug-2007

目錄

摘要(Abstract)


本提案定義了數值類抽象基類(ABC,Abstract Base Class,PEP 3119)的層次結構(hierarchy)。這里提出了 Number :> Complex :> Real :> Rational :> Integral 的層級,A :> B 意味着“A 是 B 的超類型”。這種層次結構的制定受到了 Scheme 數值類型塔的啟發。

原由(Rationale)


用數值作參數的函數應能確定這些數值的屬性,並且當語言中增加了基於類型的重載時,應能依據參數類型實現函數重載。比如,切片(slice)操作要求參數為整數類型(Integral),而 math 模塊中的函數則要求參數為實數類型(Real)。

規范(Specification)


本文定義了一組抽象基類,並給出一些方法的通常實現方式。這里用到了 PEP 3119 中的術語,但這種層次結構對於任何用於類定義的系統性解決方案都頗具意義。

標准庫中的類型檢查過程應該采用這些類,而不是采用具體(concrete)的內置類型。

數值類(Numeric Classses)


下面就從一個 Number 類開始吧,以便大家把數值的類型先模糊掉。該類只是為了便於重載,並不支持任何操作。

    class Number(metaclass=ABCMeta): pass

復數的大多數實現類都是可散列(hashable)的,但如果要絕對可靠,則必須顯式地進行檢查,驗證數值類型的層次結構是否支持可變(mutable)數值。

    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 heterogenous 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 real 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 的抽象基類表明,數值在層次結構中處於實數的位置,並且支持內置 float 類型的全部操作。除了 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 了。

    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

運算方法和魔法方法的改動(Changes to operations and magic methods)


為了支持 float 和 int (Real 和 Integral)之間更細微的差別,下面給出一些新的魔法方法,以供相應的庫函數調用。這些方法都會返回 Integral 而非 Real。

  1. __trunc__(self),由新的內置方法 trunc(x) 調用,返回 0 和 x 之間離 x 最近的整數。

  2. __floor__(self),由 math.floor(x) 調用,返回 <= x 的最大整數。

  3. __ceil__(self),由 math.ceil(x) 調用,返回 >= x 的最大整數。

  4. __round__(self),由 round(x) 調用,返回離 x 最近的整數,半數取整將依數據類型而定。在 3.0 版中 float 將會修改為半數向偶數取整。這還有一個帶兩個參數的版本 __round__(self, ndigits),由 round(x, ndigits) 調用,將會返回實數。

在 2.6 版中,math.floormath.ceilround 將仍舊返回浮點數。

float 實現的 int() 轉換等效於 trunc()。通常 int() 轉換應該先嘗試 __int__(),若不存在再嘗試 __trunc__()

complex.__{divmod,mod,floordiv,int,float}__ 也消失了。若是能提供一個好的錯誤信息就完美了,但更重要的是別再出現在 help(complex) 里了。

實現類型時的注意事項(Notes for type implementors)


實現時應注意讓相等的數值確實相等,並將他們散列為相同值。如果實數有兩種不同的擴展實現,就可能有些微妙了。比如,復數類型如下實現 hash() 就較為合理:

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

但對那些超出內置復數范圍或精度的值應該多加小心。

加入其他數值型抽象基類(Adding More Numeric ABCs)


當然,數值型還可能會有更多的抽象基類,如果不考慮添加這些類的能力,數值類型的層次結構會很差勁。比如可以在 ComplexReal 之間加入以下 MyFoo

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

算術運算的實現(Implementing the arithmetic operations)


在混合運算時,要么調用兩個參數類型已知的實現,要么先把兩個參數都轉換為最接近的內置類型再執行運算,這便是應該實現的算術運算。對於整型的子類型,這意味着 addradd 應該定義如下:

    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

對於復數類的子類,混合運算有五種不同的情況。這里將把上述所有未引用 MyIntegral 和 OtherTypeIKnowAbout 的代碼作為“樣板”(boilerplate)。a 將會是 A 的實例,而 AComplex 的子類型(a : A <: Complex),同樣 b : B <: Complex。於是 a + b 將會被如下處理:

  1. 如果 A 定義了可以接受 b 的 add 方法,萬事大吉。
  2. 如果 A 降級(fall back)到采用樣板代碼,並要由 add 返回結果值,那么就算 B 定義了更明智的 radd 也會被忽略,於是樣板代碼應該返回 add 得出的 NotImplemented。(或者 A 可能壓根兒就不去實現 add
  3. 然后就輪到 B 的 radd。如果能接受 a 則萬事大吉。
  4. 如果 B 降級到采用樣板代碼,因為沒有其他方法可供嘗試,所以這時會采用默認的實現代碼。
  5. 如果 B <: A,Python 會在 A.__add__ 之前先嘗試調用 B.__radd__。這種做法沒有問題,因為 B 的方法是在了解 A 的情況下實現的,因此它能夠在傳遞給 Complex 之前處理這些實例。

如果 A<:ComplexB<:Real 不再共用其他信息,那么共用內置 Complex 類型的相關運算方法就是合理的,兩者的 radd 都會落到 Complex 中,因此 a+b == b+a

未被接受的其他提案(Rejected Alternatives)


在 Number 形成之前,本 PEP 的最初版本曾經定義了一種受 Haskell Numeric Prelude 啟發而得的數值類型層次結構,其中包括 MonoidUnderPlus、AdditiveGroup、Ring、Field,以及之前提及的其他幾種數值類型。原本是希望這些對使用向量和矩陣的人有用,但是 NumPy 社區確實對此不感興趣,同時還遇到了一個問題,即便 xX <: MonoidUnderPlus 的實例,y 也是 Y <: MonoidUnderPlus 的實例,但 x + y 仍有可能沒有意義。

於是后來 Number 又增加了更多分支,將高斯整數(Gaussian Integer)和 Z/nZ 之類的數值包含了進去,他們可能屬於 Complex 但不一定要支持除法之類的運算。社區認為對於 Python 而言這種做法太復雜了,因此本提案現在縮小了規模,更接近於 Scheme 數值類型塔

Decimal 類型(The Decimal Type)


經與作者協商,決定目前不應將 Decimal 類型加入數值類型塔中。

參考文獻(References)


抽象基類介紹(http://www.python.org/dev/peps/pep-3119/)

可能的 Python 3K 類樹?Bill Janssen 寫的 Wiki(http://wiki.python.org/moin/AbstractBaseClasses)

NumericPrelude:數值類層次結構的實驗性替代方案(http://darcs.haskell.org/numericprelude/docs/html/index.html)

Scheme 數值類型塔(https://groups.csail.mit.edu/mac/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50)

致謝(Acknowledgements)


感謝 Neal Norwitz 第一時間鼓勵我寫下本 PEP,感謝 Travis Oliphant 指出 Numpy 用戶對數(algebraic)的概念真不太在意,感謝 Alan Isaac 提醒我 Scheme 已經完成了本文相關體系的構建,感謝 Guido van Rossum 和郵件列表中的很多人幫我完善了概念。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM