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 [email protected]
Status: Final
Type: Standards Track
Created: 23-Apr-2007
Post-History: 25-Apr-2007, 16-May-2007, 02-Aug-2007
目錄
- 摘要(Abstract)
- 原由(Rationale)
- 規范(Specification)
- 數值類(Numeric Classses)
- 運算方法和魔法方法的改動(Changes to operations and magic methods)
- 實作型別時的注意事項(Notes for type implementors)
- 加入其他數值型抽象基類(Adding More Numeric ABCs)
- 算術運算的實作(Implementing the arithmetic operations)
- 未被接受的其他提案(Rejected Alternatives)
- Decimal 型別(The Decimal Type)
- 參考文獻(References)
- 致謝(Acknowledgements)
- 著作權(Copyright)
摘要(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,
-
__trunc__(self),由新的內置方法trunc(x)呼叫,回傳 0 和x之間離x最近的整數, -
__floor__(self),由math.floor(x)呼叫,回傳<= x的最大整數, -
__ceil__(self),由math.ceil(x)呼叫,回傳>= x的最大整數, -
__round__(self),由round(x)呼叫,回傳離x最近的整數,半數取整將依資料型別而定,在 3.0 版中float將會修改為半數向偶數取整,這還有一個帶兩個引數的版本__round__(self, ndigits),由round(x, ndigits)呼叫,將會回傳實數,
在 2.6 版中,math.floor、math.ceil 和 round 將仍舊回傳浮點數,
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)
當然,數值型還可能會有更多的抽象基類,如果不考慮添加這些類的能力,數值型別的層次結構會很差勁,比如可以在 Complex 和 Real 之間加入以下 MyFoo:
class MyFoo(Complex): ...
MyFoo.register(Real)
算術運算的實作(Implementing the arithmetic operations)
在混合運算時,要么呼叫兩個引數型別已知的實作,要么先把兩個引數都轉換為最接近的內置型別再執行運算,這便是應該實作的算術運算,對于整型的子型別,這意味著 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
對于復數類的子類,混合運算有五種不同的情況,這里將把上述所有未參考 MyIntegral 和 OtherTypeIKnowAbout 的代碼作為“樣板”(boilerplate),a 將會是 A 的實體,而 A 是 Complex 的子型別(a : A <: Complex),同樣 b : B <: Complex,于是 a + b 將會被如下處理:
- 如果 A 定義了可以接受 b 的 add 方法,萬事大吉,
- 如果 A 降級(fall back)到采用樣板代碼,并要由 add 回傳結果值,那么就算 B 定義了更明智的 radd 也會被忽略,于是樣板代碼應該回傳 add 得出的 NotImplemented,(或者 A 可能壓根兒就不去實作 add,
- 然后就輪到 B 的 radd,如果能接受 a 則萬事大吉,
- 如果 B 降級到采用樣板代碼,因為沒有其他方法可供嘗試,所以這時會采用默認的實作代碼,
- 如果 B <: A,Python 會在
A.__add__之前先嘗試呼叫B.__radd__,這種做法沒有問題,因為 B 的方法是在了解 A 的情況下實作的,因此它能夠在傳遞給 Complex 之前處理這些實體,
如果 A<:Complex 和 B<:Real 不再共用其他資訊,那么共用內置 Complex 型別的相關運算方法就是合理的,兩者的 radd 都會落到 Complex 中,因此 a+b == b+a,
未被接受的其他提案(Rejected Alternatives)
在 Number 形成之前,本 PEP 的最初版本曾經定義了一種受 Haskell Numeric Prelude 啟發而得的數值型別層次結構,其中包括 MonoidUnderPlus、AdditiveGroup、Ring、Field,以及之前提及的其他幾種數值型別,原本是希望這些對使用向量和矩陣的人有用,但是 NumPy 社區確實對此不感興趣,同時還遇到了一個問題,即便 x 是 X <: 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 和郵件串列中的很多人幫我完善了概念,
著作權(Copyright)
本文已在公共領域發布,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/183600.html
標籤:Python
