国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

SICP Python 描述 2.7 泛用方法

leanote / 664人閱讀

摘要:使用消息傳遞,我們就能使抽象數(shù)據(jù)類型直接擁有行為。構(gòu)造器以類似的方式實(shí)現(xiàn)它在參數(shù)上調(diào)用了叫做的方法。抽象數(shù)據(jù)類型允許我們?cè)跀?shù)據(jù)表示和用于操作數(shù)據(jù)的函數(shù)之間構(gòu)造界限。

2.7 泛用方法

來源:2.7 Generic Operations

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

這一章中我們引入了復(fù)合數(shù)據(jù)類型,以及由構(gòu)造器和選擇器實(shí)現(xiàn)的數(shù)據(jù)抽象機(jī)制。使用消息傳遞,我們就能使抽象數(shù)據(jù)類型直接擁有行為。使用對(duì)象隱喻,我們可以將數(shù)據(jù)的表示和用于操作數(shù)據(jù)的方法綁定在一起,從而使數(shù)據(jù)驅(qū)動(dòng)的程序模塊化,并帶有局部狀態(tài)。

但是,我們?nèi)匀槐仨氄故荆覀兊膶?duì)象系統(tǒng)允許我們?cè)诖笮统绦蛑徐`活組合不同類型的對(duì)象。點(diǎn)運(yùn)算符的消息傳遞僅僅是一種用于使用多個(gè)對(duì)象構(gòu)建組合表達(dá)式的方式。這一節(jié)中,我們會(huì)探索一些用于組合和操作不同類型對(duì)象的方式。

2.7.1 字符串轉(zhuǎn)換

我們?cè)谶@一章最開始說,對(duì)象值的行為應(yīng)該類似它所表達(dá)的數(shù)據(jù),包括產(chǎn)生它自己的字符串表示。數(shù)據(jù)值的字符串表示在類似 Python 的交互式語言中尤其重要,其中“讀取-求值-打印”的循環(huán)需要每個(gè)值都擁有某種字符串表示形式。

字符串值為人們的信息交流提供了基礎(chǔ)的媒介。字符序列可以在屏幕上渲染,打印到紙上,大聲朗讀,轉(zhuǎn)換為盲文,或者以莫爾茲碼廣播。字符串對(duì)編程而言也非常基礎(chǔ),因?yàn)樗鼈兛梢员硎?Python 表達(dá)式。對(duì)于一個(gè)對(duì)象,我們可能希望生成一個(gè)字符串,當(dāng)作為 Python 表達(dá)式解釋時(shí),求值為等價(jià)的對(duì)象。

Python 規(guī)定,所有對(duì)象都應(yīng)該能夠產(chǎn)生兩種不同的字符串表示:一種是人類可解釋的文本,另一種是 Python 可解釋的表達(dá)式。字符串的構(gòu)造函數(shù)str返回人類可讀的字符串。在可能的情況下,repr函數(shù)返回一個(gè) Python 表達(dá)式,它可以求值為等價(jià)的對(duì)象。repr的文檔字符串解釋了這個(gè)特性:

repr(object) -> string

Return the canonical string representation of the object.
For most object types, eval(repr(object)) == object.

在表達(dá)式的值上調(diào)用repr的結(jié)果就是 Python 在交互式會(huì)話中打印的東西。

>>> 12e12
12000000000000.0
>>> print(repr(12e12))
12000000000000.0

在不存在任何可以求值為原始值的表達(dá)式的情況中,Python 會(huì)產(chǎn)生一個(gè)代理:

>>> repr(min)
""

str構(gòu)造器通常與repr相同,但是有時(shí)會(huì)提供更加可解釋的文本表示。例如,我們可以看到strrepr對(duì)于日期的不同:

>>> from datetime import date
>>> today = date(2011, 9, 12)
>>> repr(today)
"datetime.date(2011, 9, 12)"
>>> str(today)
"2011-09-12"

repr函數(shù)的定義出現(xiàn)了新的挑戰(zhàn):我們希望它對(duì)所有數(shù)據(jù)類型都正確應(yīng)用,甚至是那些在repr實(shí)現(xiàn)時(shí)還不存在的類型。我們希望它像一個(gè)多態(tài)函數(shù),可以作用于許多(多)不同形式(態(tài))的數(shù)據(jù)。

消息傳遞提供了這個(gè)問題的解決方案:repr函數(shù)在參數(shù)上調(diào)用叫做__repr__的函數(shù)。

>>> today.__repr__()
"datetime.date(2011, 9, 12)"

通過在用戶定義的類上實(shí)現(xiàn)同一方法,我們就可以將repr的適用性擴(kuò)展到任何我們以后創(chuàng)建的類。這個(gè)例子強(qiáng)調(diào)了消息傳遞的另一個(gè)普遍的好處:就是它提供了一種機(jī)制,用于將現(xiàn)有函數(shù)的職責(zé)范圍擴(kuò)展到新的對(duì)象。

str構(gòu)造器以類似的方式實(shí)現(xiàn):它在參數(shù)上調(diào)用了叫做__str__的方法。

>>> today.__str__()
"2011-09-12"

這些多態(tài)函數(shù)是一個(gè)更普遍原則的例子:特定函數(shù)應(yīng)該作用于多種數(shù)據(jù)類型。這里舉例的消息傳遞方法僅僅是多態(tài)函數(shù)實(shí)現(xiàn)家族的一員。本節(jié)剩下的部分會(huì)探索一些備選方案。

2.7.2 多重表示

使用對(duì)象或函數(shù)的數(shù)據(jù)抽象是用于管理復(fù)雜性的強(qiáng)大工具。抽象數(shù)據(jù)類型允許我們?cè)跀?shù)據(jù)表示和用于操作數(shù)據(jù)的函數(shù)之間構(gòu)造界限。但是,在大型程序中,對(duì)于程序中的某種數(shù)據(jù)類型,提及“底層表示”可能不總是有意義。首先,一個(gè)數(shù)據(jù)對(duì)象可能有多種實(shí)用的表示,而且我們可能希望設(shè)計(jì)能夠處理多重表示的系統(tǒng)。

為了選取一個(gè)簡(jiǎn)單的示例,復(fù)數(shù)可以用兩種幾乎等價(jià)的方式來表示:直角坐標(biāo)(虛部和實(shí)部)以及極坐標(biāo)(模和角度)。有時(shí)直角坐標(biāo)形式更加合適,而有時(shí)極坐標(biāo)形式更加合適。復(fù)數(shù)以兩種方式表示,而操作復(fù)數(shù)的函數(shù)可以處理每種表示,這樣一個(gè)系統(tǒng)確實(shí)比較合理。

更重要的是,大型軟件系統(tǒng)工程通常由許多人設(shè)計(jì),并花費(fèi)大量時(shí)間,需求的主題隨時(shí)間而改變。在這樣的環(huán)境中,每個(gè)人都事先同意數(shù)據(jù)表示的方案是不可能的。除了隔離使用和表示的數(shù)據(jù)抽象的界限,我們需要隔離不同設(shè)計(jì)方案的界限,以及允許不同方案在一個(gè)程序中共存。進(jìn)一步,由于大型程序通常通過組合已存在的模塊創(chuàng)建,這些模塊會(huì)多帶帶設(shè)計(jì),我們需要一種慣例,讓程序員將模塊遞增地組合為大型系統(tǒng)。也就是說,不需要重復(fù)設(shè)計(jì)或?qū)崿F(xiàn)這些模塊。

我們以最簡(jiǎn)單的復(fù)數(shù)示例開始。我們會(huì)看到,消息傳遞在維持“復(fù)數(shù)”對(duì)象的抽象概念時(shí),如何讓我們?yōu)閺?fù)數(shù)的表示設(shè)計(jì)出分離的直角坐標(biāo)和極坐標(biāo)表示。我們會(huì)通過使用泛用選擇器為復(fù)數(shù)定義算數(shù)函數(shù)(add_complexmul_complex)來完成它。泛用選擇器可訪問復(fù)數(shù)的一部分,獨(dú)立于數(shù)值表示的方式。所產(chǎn)生的復(fù)數(shù)系統(tǒng)包含兩種不同類型的抽象界限。它們隔離了高階操作和低階表示。此外,也有一個(gè)垂直的界限,它使我們能夠獨(dú)立設(shè)計(jì)替代的表示。

作為邊注,我們正在開發(fā)一個(gè)系統(tǒng),它在復(fù)數(shù)上執(zhí)行算數(shù)運(yùn)算,作為一個(gè)簡(jiǎn)單但不現(xiàn)實(shí)的使用泛用操作的例子。復(fù)數(shù)類型實(shí)際上在 Python 中已經(jīng)內(nèi)建了,但是這個(gè)例子中我們?nèi)匀蛔约簩?shí)現(xiàn)。

就像有理數(shù)那樣,復(fù)數(shù)可以自然表示為偶對(duì)。復(fù)數(shù)集可以看做帶有兩個(gè)正交軸,實(shí)數(shù)軸和虛數(shù)軸的二維空間。根據(jù)這個(gè)觀點(diǎn),復(fù)數(shù)z = x + y * i(其中i*i = -1)可以看做平面上的點(diǎn),它的實(shí)數(shù)為x,虛部為y。復(fù)數(shù)加法涉及到將它們的實(shí)部和虛部相加。

對(duì)復(fù)數(shù)做乘法時(shí),將復(fù)數(shù)以極坐標(biāo)表示為模和角度更加自然。兩個(gè)復(fù)數(shù)的乘積是,將一個(gè)復(fù)數(shù)按照另一個(gè)的長(zhǎng)度作為因數(shù)拉伸,之后按照另一個(gè)的角度來旋轉(zhuǎn)它的所得結(jié)果。

所以,復(fù)數(shù)有兩種不同表示,它們適用于不同的操作。然而,從一些人編寫使用復(fù)數(shù)的程序的角度來看,數(shù)據(jù)抽象的原則表明,所有操作復(fù)數(shù)的運(yùn)算都應(yīng)該可用,無論計(jì)算機(jī)使用了哪個(gè)表示。

接口。消息傳遞并不僅僅提供用于組裝行為和數(shù)據(jù)的方式。它也允許不同的數(shù)據(jù)類型以不同方式響應(yīng)相同消息。來自不同對(duì)象,產(chǎn)生相似行為的共享消息是抽象的有力手段。

像之前看到的那樣,抽象數(shù)據(jù)類型由構(gòu)造器、選擇器和額外的行為條件定義。與之緊密相關(guān)的概念是接口,它是共享消息的集合,帶有它們含義的規(guī)定。響應(yīng)__repr____str__特殊方法的對(duì)象都實(shí)現(xiàn)了通用的接口,它們可以表示為字符串。

在復(fù)數(shù)的例子中,接口需要實(shí)現(xiàn)由四個(gè)消息組成的算數(shù)運(yùn)算:realimagmagnitudeangle。我們可以使用這些消息實(shí)現(xiàn)加法和乘法。

我們擁有兩種復(fù)數(shù)的抽象數(shù)據(jù)類型,它們的構(gòu)造器不同。

ComplexRI從實(shí)部和虛部構(gòu)造復(fù)數(shù)。

ComplexMA從模和角度構(gòu)造復(fù)數(shù)。

使用這些消息和構(gòu)造器,我們可以實(shí)現(xiàn)復(fù)數(shù)算數(shù):

>>> def add_complex(z1, z2):
        return ComplexRI(z1.real + z2.real, z1.imag + z2.imag)
>>> def mul_complex(z1, z2):
        return ComplexMA(z1.magnitude * z2.magnitude, z1.angle + z2.angle)

術(shù)語“抽象數(shù)據(jù)類型”(ADT)和“接口”的關(guān)系是微妙的。ADT 包含構(gòu)建復(fù)雜數(shù)據(jù)類的方式,以單元操作它們,并且可以選擇它們的組件。在面向?qū)ο笙到y(tǒng)中,ADT 對(duì)應(yīng)一個(gè)類,雖然我們已經(jīng)看到對(duì)象系統(tǒng)并不需要實(shí)現(xiàn) ADT。接口是一組與含義關(guān)聯(lián)的消息,并且它可能包含選擇器,也可能不包含。概念上,ADT 描述了一類東西的完整抽象表示,而接口規(guī)定了可能在許多東西之間共享的行為。

屬性(Property)。我們希望交替使用復(fù)數(shù)的兩種類型,但是對(duì)于每個(gè)數(shù)值來說,儲(chǔ)存重復(fù)的信息比較浪費(fèi)。我們希望儲(chǔ)存實(shí)部-虛部的表示或模-角度的表示之一。

Python 擁有一個(gè)簡(jiǎn)單的特性,用于從零個(gè)參數(shù)的函數(shù)憑空計(jì)算屬性(Attribute)。@property裝飾器允許函數(shù)不使用標(biāo)準(zhǔn)調(diào)用表達(dá)式語法來調(diào)用。根據(jù)實(shí)部和虛部的復(fù)數(shù)實(shí)現(xiàn)展示了這一點(diǎn)。

>>> from math import atan2
>>> class ComplexRI(object):
        def __init__(self, real, imag):
            self.real = real
            self.imag = imag
        @property
        def magnitude(self):
            return (self.real ** 2 + self.imag ** 2) ** 0.5
        @property
        def angle(self):
            return atan2(self.imag, self.real)
        def __repr__(self):
            return "ComplexRI({0}, {1})".format(self.real, self.imag)

第二種使用模和角度的實(shí)現(xiàn)提供了相同接口,因?yàn)樗憫?yīng)同一組消息。

>>> from math import sin, cos
>>> class ComplexMA(object):
        def __init__(self, magnitude, angle):
            self.magnitude = magnitude
            self.angle = angle
        @property
        def real(self):
            return self.magnitude * cos(self.angle)
        @property
        def imag(self):
            return self.magnitude * sin(self.angle)
        def __repr__(self):
            return "ComplexMA({0}, {1})".format(self.magnitude, self.angle)

實(shí)際上,我們的add_complexmul_complex實(shí)現(xiàn)并沒有完成;每個(gè)復(fù)數(shù)類可以用于任何算數(shù)函數(shù)的任何參數(shù)。對(duì)象系統(tǒng)不以任何方式顯式連接(例如通過繼承)這兩種復(fù)數(shù)類型,這需要給個(gè)注解。我們已經(jīng)通過在兩個(gè)類之間共享一組通用的消息和接口,實(shí)現(xiàn)了復(fù)數(shù)抽象。

>>> from math import pi
>>> add_complex(ComplexRI(1, 2), ComplexMA(2, pi/2))
ComplexRI(1.0000000000000002, 4.0)
>>> mul_complex(ComplexRI(0, 1), ComplexRI(0, 1))
ComplexMA(1.0, 3.141592653589793)

編碼多種表示的接口擁有良好的特性。用于每個(gè)表示的類可以獨(dú)立開發(fā);它們只需要遵循它們所共享的屬性名稱。這個(gè)接口同時(shí)是遞增的。如果另一個(gè)程序員希望向相同程序添加第三個(gè)復(fù)數(shù)表示,它們只需要使用相同屬性創(chuàng)建另一個(gè)類。

特殊方法。內(nèi)建的算數(shù)運(yùn)算符可以以一種和repr相同的方式擴(kuò)展;它們是特殊的方法名稱,對(duì)應(yīng) Python 的算數(shù)、邏輯和序列運(yùn)算的運(yùn)算符。

為了使我們的代碼更加易讀,我們可能希望在執(zhí)行復(fù)數(shù)加法和乘法時(shí)直接使用+*運(yùn)算符。將下列方法添加到兩個(gè)復(fù)數(shù)類中,這會(huì)讓這些運(yùn)算符,以及opertor模塊中的addmul函數(shù)可用。

>>> ComplexRI.__add__ = lambda self, other: add_complex(self, other)
>>> ComplexMA.__add__ = lambda self, other: add_complex(self, other)
>>> ComplexRI.__mul__ = lambda self, other: mul_complex(self, other)
>>> ComplexMA.__mul__ = lambda self, other: mul_complex(self, other)

現(xiàn)在,我們可以對(duì)我們的自定義類使用中綴符號(hào)。

>>> ComplexRI(1, 2) + ComplexMA(2, 0)
ComplexRI(3.0, 2.0)
>>> ComplexRI(0, 1) * ComplexRI(0, 1)
ComplexMA(1.0, 3.141592653589793)

擴(kuò)展閱讀。為了求解含有+運(yùn)算符的表達(dá)式,Python 會(huì)檢查表達(dá)式的左操作數(shù)和右操作數(shù)上的特殊方法。首先,Python 會(huì)檢查左操作數(shù)的__add__方法,之后檢查右操作數(shù)的__radd__方法。如果二者之一被發(fā)現(xiàn),這個(gè)方法會(huì)以另一個(gè)操作數(shù)的值作為參數(shù)調(diào)用。

在 Python 中求解含有任何類型的運(yùn)算符的表達(dá)值具有相似的協(xié)議,這包括切片符號(hào)和布爾運(yùn)算符。Python 文檔列出了完整的運(yùn)算符的方法名稱。Dive into Python 3 的特殊方法名稱一章描述了許多用于 Python 解釋器的細(xì)節(jié)。

2.7.3 泛用函數(shù)

我們的復(fù)數(shù)實(shí)現(xiàn)創(chuàng)建了兩種數(shù)據(jù)類型,它們對(duì)于add_complexmul_complex函數(shù)能夠互相轉(zhuǎn)換。現(xiàn)在我們要看看如何使用相同的概念,不僅僅定義不同表示上的泛用操作,也能用來定義不同種類、并且不共享通用結(jié)構(gòu)的參數(shù)上的泛用操作。

我們到目前為止已定義的操作將不同的數(shù)據(jù)類型獨(dú)立對(duì)待。所以,存在用于加法的獨(dú)立的包,比如兩個(gè)有理數(shù)或者兩個(gè)復(fù)數(shù)。我們沒有考慮到的是,定義類型界限之間的操作很有意義,比如將復(fù)數(shù)與有理數(shù)相加。我們經(jīng)歷了巨大的痛苦,引入了程序中各個(gè)部分的界限,便于讓它們可被獨(dú)立開發(fā)和理解。

我們希望以某種精確控制的方式引入跨類型的操作。便于在不嚴(yán)重違反抽象界限的情況下支持它們。在我們希望的結(jié)果之間可能有些矛盾:我們希望能夠?qū)⒂欣頂?shù)與復(fù)數(shù)相加,也希望能夠使用泛用的add函數(shù),正確處理所有數(shù)值類型。同時(shí),我們希望隔離復(fù)數(shù)和有理數(shù)的細(xì)節(jié),來維持程序的模塊化。

讓我們使用 Python 內(nèi)建的對(duì)象系統(tǒng)重新編寫有理數(shù)的實(shí)現(xiàn)。像之前一樣,我們?cè)谳^低層級(jí)將有理數(shù)儲(chǔ)存為分子和分母。

>>> from fractions import gcd
>>> class Rational(object):
        def __init__(self, numer, denom):
            g = gcd(numer, denom)
            self.numer = numer // g
            self.denom = denom // g
        def __repr__(self):
            return "Rational({0}, {1})".format(self.numer, self.denom)

這個(gè)新的實(shí)現(xiàn)中的有理數(shù)的加法和乘法和之前類似。

>>> def add_rational(x, y):
        nx, dx = x.numer, x.denom
        ny, dy = y.numer, y.denom
        return Rational(nx * dy + ny * dx, dx * dy)
>>> def mul_rational(x, y):
        return Rational(x.numer * y.numer, x.denom * y.denom)

類型分發(fā)。一種處理跨類型操作的方式是為每種可能的類型組合設(shè)計(jì)不同的函數(shù),操作可用于這種類型。例如,我們可以擴(kuò)展我們的復(fù)數(shù)實(shí)現(xiàn),使其提供函數(shù)用于將復(fù)數(shù)與有理數(shù)相加。我們可以使用叫做類型分發(fā)的機(jī)制更通用地提供這個(gè)功能。

類型分發(fā)的概念是,編寫一個(gè)函數(shù),首先檢測(cè)接受到的參數(shù)類型,之后執(zhí)行適用于這種類型的代碼。Python 中,對(duì)象類型可以使用內(nèi)建的type函數(shù)來檢測(cè)。

>>> def iscomplex(z):
        return type(z) in (ComplexRI, ComplexMA)
>>> def isrational(z):
        return type(z) == Rational

這里,我們依賴一個(gè)事實(shí),每個(gè)對(duì)象都知道自己的類型,并且我們可以使用Python 的type函數(shù)來獲取類型。即使type函數(shù)不可用,我們也能根據(jù)RationalComplexRIComplexMA來實(shí)現(xiàn)iscomplexisrational

現(xiàn)在考慮下面的add實(shí)現(xiàn),它顯式檢查了兩個(gè)參數(shù)的類型。我們不會(huì)在這個(gè)例子中顯式使用 Python 的特殊方法(例如__add__)。

>>> def add_complex_and_rational(z, r):
            return ComplexRI(z.real + r.numer/r.denom, z.imag)
>>> def add(z1, z2):
        """Add z1 and z2, which may be complex or rational."""
        if iscomplex(z1) and iscomplex(z2):
            return add_complex(z1, z2)
        elif iscomplex(z1) and isrational(z2):
            return add_complex_and_rational(z1, z2)
        elif isrational(z1) and iscomplex(z2):
            return add_complex_and_rational(z2, z1)
        else:
            return add_rational(z1, z2)

這個(gè)簡(jiǎn)單的類型分發(fā)方式并不是遞增的,它使用了大量的條件語句。如果另一個(gè)數(shù)值類型包含在程序中,我們需要使用新的語句重新實(shí)現(xiàn)add

我們可以創(chuàng)建更靈活的add實(shí)現(xiàn),通過以字典實(shí)現(xiàn)類型分發(fā)。要想擴(kuò)展add的靈活性,第一步是為我們的類創(chuàng)建一個(gè)tag集合,抽離兩個(gè)復(fù)數(shù)集合的實(shí)現(xiàn)。

>>> def type_tag(x):
        return type_tag.tags[type(x)]
>>> type_tag.tags = {ComplexRI: "com", ComplexMA: "com", Rational: "rat"}

下面,我們使用這些類型標(biāo)簽來索引字典,字典中儲(chǔ)存了數(shù)值加法的不同方式。字典的鍵是類型標(biāo)簽的元素,值是類型特定的加法函數(shù)。

>>> def add(z1, z2):
        types = (type_tag(z1), type_tag(z2))
        return add.implementations[types](z1, z2)

這個(gè)基于字典的分發(fā)方式是遞增的,因?yàn)?b>add.implementations和type_tag.tags總是可以擴(kuò)展。任何新的數(shù)值類型可以將自己“安裝”到現(xiàn)存的系統(tǒng)中,通過向這些字典添加新的條目。

當(dāng)我們向系統(tǒng)引入一些復(fù)雜性時(shí),我們現(xiàn)在擁有了泛用、可擴(kuò)展的add函數(shù),可以處理混合類型。

>>> add(ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> add(Rational(5, 3), Rational(1, 2))
Rational(13, 6)

數(shù)據(jù)導(dǎo)向編程。我們基于字典的add實(shí)現(xiàn)并不是特定于加法的;它不包含任何加法的直接邏輯。它只實(shí)現(xiàn)了加法操作,因?yàn)槲覀兣銮蓪?b>implementations字典和函數(shù)放到一起來執(zhí)行加法。

更通用的泛用算數(shù)操作版本會(huì)將任意運(yùn)算符作用于任意類型,并且使用字典來儲(chǔ)存多種組合的實(shí)現(xiàn)。這個(gè)完全泛用的實(shí)現(xiàn)方法的方式叫做數(shù)據(jù)導(dǎo)向編程。在我們這里,我們可以實(shí)現(xiàn)泛用加法和乘法,而不帶任何重復(fù)的邏輯。

>>> def apply(operator_name, x, y):
        tags = (type_tag(x), type_tag(y))
        key = (operator_name, tags)
        return apply.implementations[key](x, y)

在泛用的apply函數(shù)中,鍵由操作數(shù)的名稱(例如add),和參數(shù)類型標(biāo)簽的元組構(gòu)造。我們下面添加了對(duì)復(fù)數(shù)和有理數(shù)的乘法支持。

>>> def mul_complex_and_rational(z, r):
        return ComplexMA(z.magnitude * r.numer / r.denom, z.angle)
>>> mul_rational_and_complex = lambda r, z: mul_complex_and_rational(z, r)
>>> apply.implementations = {("mul", ("com", "com")): mul_complex,
                             ("mul", ("com", "rat")): mul_complex_and_rational,
                             ("mul", ("rat", "com")): mul_rational_and_complex,
                             ("mul", ("rat", "rat")): mul_rational}

我們也可以使用字典的update方法,從add中將加法實(shí)現(xiàn)添加到apply

>>> adders = add.implementations.items()
>>> apply.implementations.update({("add", tags):fn for (tags, fn) in adders})

既然已經(jīng)在單一的表中支持了 8 種不同的實(shí)現(xiàn),我們可以用它來更通用地操作有理數(shù)和復(fù)數(shù)。

>>> apply("add", ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> apply("mul", Rational(1, 2), ComplexMA(10, 1))
ComplexMA(5.0, 1)

這個(gè)數(shù)據(jù)導(dǎo)向的方式管理了跨類型運(yùn)算符的復(fù)雜性,但是十分麻煩。使用這個(gè)一個(gè)系統(tǒng),引入新類型的開銷不僅僅是為類型編寫方法,還有實(shí)現(xiàn)跨類型操作的函數(shù)的構(gòu)造和安裝。這個(gè)負(fù)擔(dān)比起定義類型本身的操作需要更多代碼。

當(dāng)類型分發(fā)機(jī)制和數(shù)據(jù)導(dǎo)向編程的確能創(chuàng)造泛用函數(shù)的遞增實(shí)現(xiàn)時(shí),它們就不能有效隔離實(shí)現(xiàn)的細(xì)節(jié)。獨(dú)立數(shù)值類型的實(shí)現(xiàn)者需要在編程跨類型操作時(shí)考慮其他類型。組合有理數(shù)和復(fù)數(shù)嚴(yán)格上并不是每種類型的范圍。在類型中制定一致的責(zé)任分工政策,在帶有多種類型和跨類型操作的系統(tǒng)設(shè)計(jì)中是大勢(shì)所趨。

強(qiáng)制轉(zhuǎn)換。在完全不相關(guān)的類型執(zhí)行完全不相關(guān)的操作的一般情況中,實(shí)現(xiàn)顯式的跨類型操作,盡管可能非常麻煩,是人們所希望的最佳方案。幸運(yùn)的是,我們有時(shí)可以通過利用類型系統(tǒng)中隱藏的額外結(jié)構(gòu)來做得更好。不同的數(shù)據(jù)類通常并不是完全獨(dú)立的,可能有一些方式,一個(gè)類型的對(duì)象通過它會(huì)被看做另一種類型的對(duì)象。這個(gè)過程叫做強(qiáng)制轉(zhuǎn)換。例如,如果我們被要求將一個(gè)有理數(shù)和一個(gè)復(fù)數(shù)通過算數(shù)來組合,我們可以將有理數(shù)看做虛部為零的復(fù)數(shù)。通過這樣做,我們將問題轉(zhuǎn)換為兩個(gè)復(fù)數(shù)組合的問題,這可以通過add_complexmul_complex由經(jīng)典的方法處理。

通常,我們可以通過設(shè)計(jì)強(qiáng)制轉(zhuǎn)換函數(shù)來實(shí)現(xiàn)這個(gè)想法。強(qiáng)制轉(zhuǎn)換函數(shù)將一個(gè)類型的對(duì)象轉(zhuǎn)換為另一個(gè)類型的等價(jià)對(duì)象。這里是一個(gè)典型的強(qiáng)制轉(zhuǎn)換函數(shù),它將有理數(shù)轉(zhuǎn)換為虛部為零的復(fù)數(shù)。

>>> def rational_to_complex(x):
        return ComplexRI(x.numer/x.denom, 0)

現(xiàn)在,我們可以定義強(qiáng)制轉(zhuǎn)換函數(shù)的字典。這個(gè)字典可以在更多的數(shù)值類型引入時(shí)擴(kuò)展。

>>> coercions = {("rat", "com"): rational_to_complex}

任意類型的數(shù)據(jù)對(duì)象不可能轉(zhuǎn)換為每個(gè)其它類型的對(duì)象。例如,沒有辦法將任意的復(fù)數(shù)強(qiáng)制轉(zhuǎn)換為有理數(shù),所以在coercions字典中應(yīng)該沒有這種轉(zhuǎn)換的實(shí)現(xiàn)。

使用coercions字典,我們可以編寫叫做coerce_apply的函數(shù),它試圖將參數(shù)強(qiáng)制轉(zhuǎn)換為相同類型的值,之后僅僅調(diào)用運(yùn)算符。coerce_apply 的實(shí)現(xiàn)字典不包含任何跨類型運(yùn)算符的實(shí)現(xiàn)。

>>> def coerce_apply(operator_name, x, y):
        tx, ty = type_tag(x), type_tag(y)
        if tx != ty:
            if (tx, ty) in coercions:
                tx, x = ty, coercions[(tx, ty)](x)
            elif (ty, tx) in coercions:
                ty, y = tx, coercions[(ty, tx)](y)
            else:
                return "No coercion possible."
        key = (operator_name, tx)
        return coerce_apply.implementations[key](x, y)

coerce_applyimplementations僅僅需要一個(gè)類型標(biāo)簽,因?yàn)樗鼈兗僭O(shè)兩個(gè)值都共享相同的類型標(biāo)簽。所以,我們僅僅需要四個(gè)實(shí)現(xiàn)來支持復(fù)數(shù)和有理數(shù)上的泛用算數(shù)。

>>> coerce_apply.implementations = {("mul", "com"): mul_complex,
                                    ("mul", "rat"): mul_rational,
                                    ("add", "com"): add_complex,
                                    ("add", "rat"): add_rational}

就地使用這些實(shí)現(xiàn),coerce_apply 可以代替apply

>>> coerce_apply("add", ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> coerce_apply("mul", Rational(1, 2), ComplexMA(10, 1))
ComplexMA(5.0, 1.0)

這個(gè)強(qiáng)制轉(zhuǎn)換的模式比起顯式定義跨類型運(yùn)算符的方式具有優(yōu)勢(shì)。雖然我們?nèi)匀恍枰幊虖?qiáng)制轉(zhuǎn)換函數(shù)來關(guān)聯(lián)類型,我們僅僅需要為每對(duì)類型編寫一個(gè)函數(shù),而不是為每個(gè)類型組合和每個(gè)泛用方法編寫不同的函數(shù)。我們所期望的是,類型間的合理轉(zhuǎn)換僅僅依賴于類型本身,而不是要調(diào)用的特定操作。

強(qiáng)制轉(zhuǎn)換的擴(kuò)展會(huì)帶來進(jìn)一步的優(yōu)勢(shì)。一些更復(fù)雜的強(qiáng)制轉(zhuǎn)換模式并不僅僅試圖將一個(gè)類型強(qiáng)制轉(zhuǎn)換為另一個(gè),而是將兩個(gè)不同類型強(qiáng)制轉(zhuǎn)換為第三個(gè)。想一想菱形和長(zhǎng)方形:每個(gè)都不是另一個(gè)的特例,但是兩個(gè)都可以看做平行四邊形。另一個(gè)強(qiáng)制轉(zhuǎn)換的擴(kuò)展是迭代的強(qiáng)制轉(zhuǎn)換,其中一個(gè)數(shù)據(jù)類型通過媒介類型被強(qiáng)制轉(zhuǎn)換為另一種。一個(gè)整數(shù)可以轉(zhuǎn)換為一個(gè)實(shí)數(shù),通過首先轉(zhuǎn)換為有理數(shù),接著將有理數(shù)轉(zhuǎn)換為實(shí)數(shù)。這種方式的鏈?zhǔn)綇?qiáng)制轉(zhuǎn)換降低了程序所需的轉(zhuǎn)換函數(shù)總數(shù)。

雖然它具有優(yōu)勢(shì),強(qiáng)制轉(zhuǎn)換也有潛在的缺陷。例如,強(qiáng)制轉(zhuǎn)換函數(shù)在調(diào)用時(shí)會(huì)丟失信息。在我們的例子中,有理數(shù)是精確表示,但是當(dāng)它們轉(zhuǎn)換為復(fù)數(shù)時(shí)會(huì)變得近似。

一些編程語言擁有內(nèi)建的強(qiáng)制轉(zhuǎn)換函數(shù)。實(shí)際上,Python 的早期版本擁有對(duì)象上的__coerce__特殊方法。最后,內(nèi)建強(qiáng)制轉(zhuǎn)換系統(tǒng)的復(fù)雜性并不能支持它的使用,所以被移除了。反之,特定的操作按需強(qiáng)制轉(zhuǎn)換它們的參數(shù)。運(yùn)算符被實(shí)現(xiàn)為用戶定義類上的特殊方法,比如__add____mul__。這完全取決于你,取決于用戶來決定是否使用類型分發(fā),數(shù)據(jù)導(dǎo)向編程,消息傳遞,或者強(qiáng)制轉(zhuǎn)換來在你的程序中實(shí)現(xiàn)泛用函數(shù)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/38164.html

相關(guān)文章

  • SICP Python 描述 第三章 計(jì)算機(jī)程序的構(gòu)造和解釋 3.1 引言

    摘要:為通用語言設(shè)計(jì)解釋器的想法可能令人畏懼。但是,典型的解釋器擁有簡(jiǎn)潔的通用結(jié)構(gòu)兩個(gè)可變的遞歸函數(shù),第一個(gè)求解環(huán)境中的表達(dá)式,第二個(gè)在參數(shù)上調(diào)用函數(shù)。這一章接下來的兩節(jié)專注于遞歸函數(shù)和數(shù)據(jù)結(jié)構(gòu),它們是理解解釋器設(shè)計(jì)的基礎(chǔ)。 3.1 引言 來源:3.1 Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 第一章和第二章描述了編程的兩個(gè)基本元素:數(shù)據(jù)和函數(shù)之間的...

    v1 評(píng)論0 收藏0
  • SICP Python 描述 2.3 序列

    摘要:序列不是特定的抽象數(shù)據(jù)類型,而是不同類型共有的一組行為。不像抽象數(shù)據(jù)類型,我們并沒有闡述如何構(gòu)造序列。這兩個(gè)選擇器和一個(gè)構(gòu)造器,以及一個(gè)常量共同實(shí)現(xiàn)了抽象數(shù)據(jù)類型的遞歸列表。 2.3 序列 來源:2.3 Sequences 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 序列是數(shù)據(jù)值的順序容器。不像偶對(duì)只有兩個(gè)元素,序列可以擁有任意(但是有限)個(gè)有序元素。 序列在計(jì)算機(jī)科學(xué)中...

    AlexTuan 評(píng)論0 收藏0
  • SICP Python 描述 第二章 使用對(duì)象構(gòu)建抽象 2.1 引言

    摘要:對(duì)象表示信息,但是同時(shí)和它們所表示的抽象概念行為一致。通過綁定行為和信息,對(duì)象提供了可靠獨(dú)立的日期抽象。名稱來源于實(shí)數(shù)在中表示的方式浮點(diǎn)表示。另一方面,對(duì)象可以表示很大范圍內(nèi)的分?jǐn)?shù),但是不能表示所有有理數(shù)。 2.1 引言 來源:2.1 Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 在第一章中,我們專注于計(jì)算過程,以及程序設(shè)計(jì)中函數(shù)的作用。我們看到了...

    phoenixsky 評(píng)論0 收藏0
  • SICP Python 描述 1.4 實(shí)踐指南:函數(shù)的藝術(shù)

    摘要:實(shí)踐指南函數(shù)的藝術(shù)來源譯者飛龍協(xié)議函數(shù)是所有程序的要素,無論規(guī)模大小,并且在編程語言中作為我們表達(dá)計(jì)算過程的主要媒介。目前為止,我們討論了函數(shù)的形式特性,以及它們?nèi)绾问褂谩5谝恍忻枋龊瘮?shù)的任務(wù)。 1.4 實(shí)踐指南:函數(shù)的藝術(shù) 來源:1.4 Practical Guidance: The Art of the Function 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 函...

    lemon 評(píng)論0 收藏0
  • SICP Python描述 1.1 引言

    摘要:另一個(gè)賦值語句將名稱關(guān)聯(lián)到出現(xiàn)在莎士比亞劇本中的所有去重詞匯的集合,總計(jì)個(gè)。表達(dá)式是一個(gè)復(fù)合表達(dá)式,計(jì)算出正序或倒序出現(xiàn)的莎士比亞詞匯集合。在意圖上并沒有按照莎士比亞或者回文來設(shè)計(jì),但是它極大的靈活性讓我們用極少的代碼處理大量文本。 1.1 引言 來源:1.1 Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 計(jì)算機(jī)科學(xué)是一個(gè)極其寬泛的學(xué)科。全球的分布...

    xumenger 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<