摘要:類似消息傳遞中的分發(fā)字典,對(duì)象響應(yīng)行為請(qǐng)求。消息傳遞和點(diǎn)表達(dá)式方法定義在類中,而實(shí)例屬性通常在構(gòu)造器中賦值,二者都是面向?qū)ο缶幊痰幕驹亍J褂脦в袃?nèi)建對(duì)象系統(tǒng)語(yǔ)言的優(yōu)點(diǎn)是,消息傳遞能夠和其它語(yǔ)言特性,例如賦值語(yǔ)句無(wú)縫對(duì)接。
2.5 面向?qū)ο缶幊?/b>
來(lái)源:2.5 Object-Oriented Programming
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
面向?qū)ο缶幊蹋∣OP)是一種用于組織程序的方法,它組合了這一章引入的許多概念。就像抽象數(shù)據(jù)類型那樣,對(duì)象創(chuàng)建了數(shù)據(jù)使用和實(shí)現(xiàn)之間的抽象界限。類似消息傳遞中的分發(fā)字典,對(duì)象響應(yīng)行為請(qǐng)求。就像可變的數(shù)據(jù)結(jié)構(gòu),對(duì)象擁有局部狀態(tài),并且不能直接從全局環(huán)境訪問。Python 對(duì)象系統(tǒng)提供了新的語(yǔ)法,更易于為組織程序?qū)崿F(xiàn)所有這些實(shí)用的技巧。
但是對(duì)象系統(tǒng)不僅僅提供了便利;它也為程序設(shè)計(jì)添加了新的隱喻,其中程序中的幾個(gè)部分彼此交互。每個(gè)對(duì)象將局部狀態(tài)和行為綁定,以一種方式在數(shù)據(jù)抽象背后隱藏二者的復(fù)雜性。我們的約束程序的例子通過在約束和連接器之前傳遞消息,產(chǎn)生了這種隱喻。Python 對(duì)象系統(tǒng)使用新的途徑擴(kuò)展了這種隱喻,來(lái)表達(dá)程序的不同部分如何互相關(guān)聯(lián),以及互相通信。對(duì)象不僅僅會(huì)傳遞消息,還會(huì)和其它相同類型的對(duì)象共享行為,以及從相關(guān)的類型那里繼承特性。
面向?qū)ο缶幊痰姆妒绞褂米约旱脑~匯來(lái)強(qiáng)化對(duì)象隱喻。我們已經(jīng)看到了,對(duì)象是擁有方法和屬性的數(shù)據(jù)值,可以通過點(diǎn)運(yùn)算符來(lái)訪問。每個(gè)對(duì)象都擁有一個(gè)類型,叫做類。Python 中可以定義新的類,就像定義函數(shù)那樣。
2.5.1 對(duì)象和類類可以用作所有類型為該類的對(duì)象的模板。每個(gè)對(duì)象都是某個(gè)特定類的實(shí)例。我們目前使用的對(duì)象都擁有內(nèi)建類型,但是我們可以定義新的類,就像定義函數(shù)那樣。類的定義規(guī)定了在該類的對(duì)象之間共享的屬性和方法。我們會(huì)通過重新觀察銀行賬戶的例子,來(lái)介紹類的語(yǔ)句。
在介紹局部狀態(tài)時(shí),我們看到,銀行賬戶可以自然地建模為擁有balance的可變值。銀行賬戶對(duì)象應(yīng)該擁有withdraw方法,在可用的情況下,它會(huì)更新賬戶余額,并返回所請(qǐng)求的金額。我們希望添加一些額外的行為來(lái)完善賬戶抽象:銀行賬戶應(yīng)該能夠返回它的當(dāng)前余額,返回賬戶持有者的名稱,以及接受存款。
Account類允許我們創(chuàng)建銀行賬戶的多個(gè)實(shí)例。創(chuàng)建新對(duì)象實(shí)例的動(dòng)作被稱為實(shí)例化該類。Python 中實(shí)例化某個(gè)類的語(yǔ)法類似于函數(shù)的調(diào)用語(yǔ)句。這里,我們使用參數(shù)"Jim"(賬戶持有者的名稱)來(lái)調(diào)用Account。
>>> a = Account("Jim")
對(duì)象的屬性是和對(duì)象關(guān)聯(lián)的名值對(duì),它可以通過點(diǎn)運(yùn)算符來(lái)訪問。屬性特定于具體的對(duì)象,而不是類的所有對(duì)象,也叫做實(shí)例屬性。每個(gè)Account對(duì)象都擁有自己的余額和賬戶持有者名稱,它們是實(shí)例屬性的一個(gè)例子。在更寬泛的編程社群中,實(shí)例屬性可能也叫做字段、屬性或者實(shí)例變量。
>>> a.holder "Jim" >>> a.balance 0
操作對(duì)象或執(zhí)行對(duì)象特定計(jì)算的函數(shù)叫做方法。方法的副作用和返回值可能依賴或改變對(duì)象的其它屬性。例如,deposit是Account對(duì)象a上的方法。它接受一個(gè)參數(shù),即需要存入的金額,修改對(duì)象的balance屬性,并返回產(chǎn)生的余額。
>>> a.deposit(15) 15
在 OOP 中,我們說(shuō)方法可以在特定對(duì)象上調(diào)用。作為調(diào)用withdraw方法的結(jié)果,要么取錢成功,余額減少并返回,要么請(qǐng)求被拒絕,賬戶打印出錯(cuò)誤信息。
>>> a.withdraw(10) # The withdraw method returns the balance after withdrawal 5 >>> a.balance # The balance attribute has changed 5 >>> a.withdraw(10) "Insufficient funds"
像上面展示的那樣,方法的行為取決于對(duì)象屬性的改變。兩次以相同參數(shù)對(duì)withdraw的調(diào)用返回了不同的結(jié)果。
2.5.2 類的定義用戶定義的類由class語(yǔ)句創(chuàng)建,它只包含單個(gè)子句。類的語(yǔ)句定義了類的名稱和基類(會(huì)在繼承那一節(jié)討論),之后包含了定義類屬性的語(yǔ)句組:
class( ):
當(dāng)類的語(yǔ)句被執(zhí)行時(shí),新的類會(huì)被創(chuàng)建,并且在當(dāng)前環(huán)境第一幀綁定到
類通常圍繞實(shí)例屬性來(lái)組織,實(shí)例屬性是名值對(duì),不和類本身關(guān)聯(lián)但和類的每個(gè)對(duì)象關(guān)聯(lián)。通過為實(shí)例化新對(duì)象定義方法,類規(guī)定了它的對(duì)象的實(shí)例屬性。
class語(yǔ)句的
>>> class Account(object): def __init__(self, account_holder): self.balance = 0 self.holder = account_holder
Account的__init__方法有兩個(gè)形參。第一個(gè)是self,綁定到新創(chuàng)建的Account對(duì)象上。第二個(gè)參數(shù),account_holder,在被調(diào)用來(lái)實(shí)例化的時(shí)候,綁定到傳給該類的參數(shù)上。
構(gòu)造器將實(shí)例屬性名稱balance與0綁定。它也將屬性名稱holder綁定到account_holder上。形參account_holder是__init__方法的局部名稱。另一方面,通過最后一個(gè)賦值語(yǔ)句綁定的名稱holder是一直存在的,因?yàn)樗褂命c(diǎn)運(yùn)算符被存儲(chǔ)為self的屬性。
定義了Account類之后,我們就可以實(shí)例化它:
>>> a = Account("Jim")
這個(gè)對(duì)Account類的“調(diào)用”創(chuàng)建了新的對(duì)象,它是Account的實(shí)例,之后以兩個(gè)參數(shù)調(diào)用了構(gòu)造函數(shù)__init__:新創(chuàng)建的對(duì)象和字符串"Jim"。按照慣例,我們使用名稱self來(lái)命名構(gòu)造器的第一個(gè)參數(shù),因?yàn)樗壎ǖ搅吮粚?shí)例化的對(duì)象上。這個(gè)慣例在幾乎所有 Python 代碼中都適用。
現(xiàn)在,我們可以使用點(diǎn)運(yùn)算符來(lái)訪問對(duì)象的balance和holder。
>>> a.balance 0 >>> a.holder "Jim"
身份。每個(gè)新的賬戶實(shí)例都有自己的余額屬性,它的值獨(dú)立于相同類的其它對(duì)象。
>>> b = Account("Jack") >>> b.balance = 200 >>> [acc.balance for acc in (a, b)] [0, 200]
為了強(qiáng)化這種隔離,每個(gè)用戶定義類的實(shí)例對(duì)象都有個(gè)獨(dú)特的身份。對(duì)象身份使用is和is not運(yùn)算符來(lái)比較。
>>> a is a True >>> a is not b True
雖然由同一個(gè)調(diào)用來(lái)構(gòu)造,綁定到a和b的對(duì)象并不相同。通常,使用賦值將對(duì)象綁定到新名稱并不會(huì)創(chuàng)建新的對(duì)象。
>>> c = a >>> c is a True
用戶定義類的新對(duì)象只在類(比如Account)使用調(diào)用表達(dá)式被實(shí)例化的時(shí)候創(chuàng)建。
方法。對(duì)象方法也由class語(yǔ)句組中的def語(yǔ)句定義。下面,deposit和withdraw都被定義為Account類的對(duì)象上的方法:
>>> class Account(object): def __init__(self, account_holder): self.balance = 0 self.holder = account_holder def deposit(self, amount): self.balance = self.balance + amount return self.balance def withdraw(self, amount): if amount > self.balance: return "Insufficient funds" self.balance = self.balance - amount return self.balance
雖然方法定義和函數(shù)定義在聲明方式上并沒有區(qū)別,方法定義有不同的效果。由class語(yǔ)句中的def語(yǔ)句創(chuàng)建的函數(shù)值綁定到了聲明的名稱上,但是只在類的局部綁定為一個(gè)屬性。這個(gè)值可以使用點(diǎn)運(yùn)算符在類的實(shí)例上作為方法來(lái)調(diào)用。
每個(gè)方法定義同樣包含特殊的首個(gè)參數(shù)self,它綁定到方法所調(diào)用的對(duì)象上。例如,讓我們假設(shè)deposit在特定的Account對(duì)象上調(diào)用,并且傳遞了一個(gè)對(duì)象值:要存入的金額。對(duì)象本身綁定到了self上,而參數(shù)綁定到了amount上。所有被調(diào)用的方法能夠通過self參數(shù)來(lái)訪問對(duì)象,所以它們可以訪問并操作對(duì)象的狀態(tài)。
為了調(diào)用這些方法,我們?cè)俅问褂命c(diǎn)運(yùn)算符,就像下面這樣:
>>> tom_account = Account("Tom") >>> tom_account.deposit(100) 100 >>> tom_account.withdraw(90) 10 >>> tom_account.withdraw(90) "Insufficient funds" >>> tom_account.holder "Tom"
當(dāng)一個(gè)方法通過點(diǎn)運(yùn)算符調(diào)用時(shí),對(duì)象本身(這個(gè)例子中綁定到了tom_account)起到了雙重作用。首先,它決定了withdraw意味著哪個(gè)名稱;withdraw并不是環(huán)境中的名稱,而是Account類局部的名稱。其次,當(dāng)withdraw方法調(diào)用時(shí),它綁定到了第一個(gè)參數(shù)self上。求解點(diǎn)運(yùn)算符的詳細(xì)過程會(huì)在下一節(jié)中展示。
2.5.3 消息傳遞和點(diǎn)表達(dá)式方法定義在類中,而實(shí)例屬性通常在構(gòu)造器中賦值,二者都是面向?qū)ο缶幊痰幕驹亍_@兩個(gè)概念很大程度上類似于數(shù)據(jù)值的消息傳遞實(shí)現(xiàn)中的分發(fā)字典。對(duì)象使用點(diǎn)運(yùn)算符接受消息,但是消息并不是任意的、值為字符串的鍵,而是類的局部名稱。對(duì)象也擁有具名的局部狀態(tài)值(實(shí)例屬性),但是這個(gè)狀態(tài)可以使用點(diǎn)運(yùn)算符訪問和操作,并不需要在實(shí)現(xiàn)中使用nonlocal語(yǔ)句。
消息傳遞的核心概念,就是數(shù)據(jù)值應(yīng)該通過響應(yīng)消息而擁有行為,這些消息和它們所表示的抽象類型相關(guān)。點(diǎn)運(yùn)算符是 Python 的語(yǔ)法特征,它形成了消息傳遞的隱喻。使用帶有內(nèi)建對(duì)象系統(tǒng)語(yǔ)言的優(yōu)點(diǎn)是,消息傳遞能夠和其它語(yǔ)言特性,例如賦值語(yǔ)句無(wú)縫對(duì)接。我們并不需要不同的消息來(lái)“獲取”和“設(shè)置”關(guān)聯(lián)到局部屬性名稱的值;語(yǔ)言的語(yǔ)法允許我們直接使用消息名稱。
點(diǎn)表達(dá)式。類似tom_account.deposit的代碼片段叫做點(diǎn)表達(dá)式。點(diǎn)表達(dá)式包含一個(gè)表達(dá)式,一個(gè)點(diǎn)和一個(gè)名稱:
.
內(nèi)建的函數(shù)getattr也會(huì)按名稱返回對(duì)象的屬性。它是等價(jià)于點(diǎn)運(yùn)算符的函數(shù)。使用getattr,我們就能使用字符串來(lái)查找某個(gè)屬性,就像分發(fā)字典那樣:
>>> getattr(tom_account, "balance") 10
我們也可以使用hasattr測(cè)試對(duì)象是否擁有某個(gè)具名屬性:
>>> hasattr(tom_account, "deposit") True
對(duì)象的屬性包含所有實(shí)例屬性,以及所有定義在類中的屬性(包括方法)。方法是需要特別處理的類的屬性。
方法和函數(shù)。當(dāng)一個(gè)方法在對(duì)象上調(diào)用時(shí),對(duì)象隱式地作為第一個(gè)參數(shù)傳遞給方法。也就是說(shuō),點(diǎn)運(yùn)算符左邊值為
為了自動(dòng)實(shí)現(xiàn)self的綁定,Python 區(qū)分函數(shù)和綁定方法。我們已經(jīng)在這門課的開始創(chuàng)建了前者,而后者在方法調(diào)用時(shí)將對(duì)象和函數(shù)組合到一起。綁定方法的值已經(jīng)將第一個(gè)函數(shù)關(guān)聯(lián)到所調(diào)用的實(shí)例,當(dāng)方法調(diào)用時(shí)實(shí)例會(huì)被命名為self。
通過在點(diǎn)運(yùn)算符的返回值上調(diào)用type,我們可以在交互式解釋器中看到它們的差異。作為類的屬性,方法只是個(gè)函數(shù),但是作為實(shí)例屬性,它是綁定方法:
>>> type(Account.deposit)>>> type(tom_account.deposit)
這兩個(gè)結(jié)果的唯一不同點(diǎn)是,前者是個(gè)標(biāo)準(zhǔn)的二元函數(shù),帶有參數(shù)self和amount。后者是一元方法,當(dāng)方法被調(diào)用時(shí),名稱self自動(dòng)綁定到了名為tom_account的對(duì)象上,而名稱amount會(huì)被綁定到傳遞給方法的參數(shù)上。這兩個(gè)值,無(wú)論函數(shù)值或綁定方法的值,都和相同的deposit函數(shù)體所關(guān)聯(lián)。
我們可以以兩種方式調(diào)用deposit:作為函數(shù)或作為綁定方法。在前者的例子中,我們必須為self參數(shù)顯式提供實(shí)參。而對(duì)于后者,self參數(shù)已經(jīng)自動(dòng)綁定了。
>>> Account.deposit(tom_account, 1001) # The deposit function requires 2 arguments 1011 >>> tom_account.deposit(1000) # The deposit method takes 1 argument 2011
函數(shù)getattr的表現(xiàn)就像運(yùn)算符那樣:它的第一個(gè)參數(shù)是對(duì)象,而第二個(gè)參數(shù)(名稱)是定義在類中的方法。之后,getattr返回綁定方法的值。另一方面,如果第一個(gè)參數(shù)是個(gè)類,getattr會(huì)直接返回屬性值,它僅僅是個(gè)函數(shù)。
實(shí)踐指南:命名慣例。類名稱通常以首字母大寫來(lái)編寫(也叫作駝峰拼寫法,因?yàn)槊Q中間的大寫字母像駝峰)。方法名稱遵循函數(shù)命名的慣例,使用以下劃線分隔的小寫字母。
有的時(shí)候,有些實(shí)例變量和方法的維護(hù)和對(duì)象的一致性相關(guān),我們不想讓用戶看到或使用它們。它們并不是由類定義的一部分抽象,而是一部分實(shí)現(xiàn)。Python 的慣例規(guī)定,如果屬性名稱以下劃線開始,它只能在方法或類中訪問,而不能被類的用戶訪問。
2.5.4 類屬性有些屬性值在特定類的所有對(duì)象之間共享。這樣的屬性關(guān)聯(lián)到類本身,而不是類的任何獨(dú)立實(shí)例。例如,讓我們假設(shè)銀行以固定的利率對(duì)余額支付利息。這個(gè)利率可能會(huì)改變,但是它是在所有賬戶中共享的單一值。
類屬性由class語(yǔ)句組中的賦值語(yǔ)句創(chuàng)建,位于任何方法定義之外。在更寬泛的開發(fā)者社群中,類屬性也被叫做類變量或靜態(tài)變量。下面的類語(yǔ)句以名稱interest為Account創(chuàng)建了類屬性。
>>> class Account(object): interest = 0.02 # A class attribute def __init__(self, account_holder): self.balance = 0 self.holder = account_holder # Additional methods would be defined here
這個(gè)屬性仍舊可以通過類的任何實(shí)例來(lái)訪問。
>>> tom_account = Account("Tom") >>> jim_account = Account("Jim") >>> tom_account.interest 0.02 >>> jim_account.interest 0.02
但是,對(duì)類屬性的單一賦值語(yǔ)句會(huì)改變所有該類實(shí)例上的屬性值。
>>> Account.interest = 0.04 >>> tom_account.interest 0.04 >>> jim_account.interest 0.04
屬性名稱。我們已經(jīng)在我們的對(duì)象系統(tǒng)中引入了足夠的復(fù)雜性,我們需要規(guī)定名稱如何解析為特定的屬性。畢竟,我們可以輕易擁有同名的類屬性和實(shí)例屬性。
像我們看到的那樣,點(diǎn)運(yùn)算符由表達(dá)式、點(diǎn)和名稱組成:
.
為了求解點(diǎn)表達(dá)式:
求出點(diǎn)左邊的
如果
這個(gè)值會(huì)被返回,如果它是個(gè)函數(shù),則會(huì)返回綁定方法。
在這個(gè)求值過程中,實(shí)例屬性在類的屬性之前查找,就像局部名稱具有高于全局的優(yōu)先級(jí)。定義在類中的方法,在求值過程的第三步綁定到了點(diǎn)運(yùn)算符的對(duì)象上。在類中查找名稱的過程有額外的差異,在我們引入類繼承的時(shí)候就會(huì)出現(xiàn)。
賦值。所有包含點(diǎn)運(yùn)算符的賦值語(yǔ)句都會(huì)作用于右邊的對(duì)象。如果對(duì)象是個(gè)實(shí)例,那么賦值就會(huì)設(shè)置實(shí)例屬性。如果對(duì)象是個(gè)類,那么賦值會(huì)設(shè)置類屬性。作為這條規(guī)則的結(jié)果,對(duì)對(duì)象屬性的賦值不能影響類的屬性。下面的例子展示了這個(gè)區(qū)別。
如果我們向賬戶實(shí)例的具名屬性interest賦值,我們會(huì)創(chuàng)建屬性的新實(shí)例,它和現(xiàn)有的類屬性具有相同名稱。
>>> jim_account.interest = 0.08
這個(gè)屬性值會(huì)通過點(diǎn)運(yùn)算符返回:
>>> jim_account.interest 0.08
但是,類屬性interest會(huì)保持為原始值,它可以通過所有其他賬戶返回。
>>> tom_account.interest 0.04
類屬性interest的改動(dòng)會(huì)影響tom_account,但是jim_account的實(shí)例屬性不受影響。
>>> Account.interest = 0.05 # changing the class attribute >>> tom_account.interest # changes instances without like-named instance attributes 0.05 >>> jim_account.interest # but the existing instance attribute is unaffected 0.082.5.5 繼承
在使用 OOP 范式時(shí),我們通常會(huì)發(fā)現(xiàn),不同的抽象數(shù)據(jù)結(jié)構(gòu)是相關(guān)的。特別是,我們發(fā)現(xiàn)相似的類在特化的程度上有區(qū)別。兩個(gè)類可能擁有相似的屬性,但是一個(gè)表示另一個(gè)的特殊情況。
例如,我們可能希望實(shí)現(xiàn)一個(gè)活期賬戶,它不同于標(biāo)準(zhǔn)的賬戶。活期賬戶對(duì)每筆取款都收取額外的 $1,并且具有較低的利率。這里,我們演示上述行為:
>>> ch = CheckingAccount("Tom") >>> ch.interest # Lower interest rate for checking accounts 0.01 >>> ch.deposit(20) # Deposits are the same 20 >>> ch.withdraw(5) # withdrawals decrease balance by an extra charge 14
CheckingAccount是Account的特化。在 OOP 的術(shù)語(yǔ)中,通用的賬戶會(huì)作為CheckingAccount的基類,而CheckingAccount是Account的子類(術(shù)語(yǔ)“父類”和“超類”通常等同于“基類”,而“派生類”通常等同于“子類”)。
子類繼承了基類的屬性,但是可能覆蓋特定屬性,包括特定的方法。使用繼承,我們只需要關(guān)注基類和子類之間有什么不同。任何我們?cè)谧宇愇粗付ǖ臇|西會(huì)自動(dòng)假設(shè)和基類中相同。
繼承也在對(duì)象隱喻中有重要作用,不僅僅是一種實(shí)用的組織方式。繼承意味著在類之間表達(dá)“is-a”關(guān)系,它和“has-a”關(guān)系相反。活期賬戶是(is-a)一種特殊類型的賬戶,所以讓CheckingAccount繼承Account是繼承的合理使用。另一方面,銀行擁有(has-a)所管理的銀行賬戶的列表,所以二者都不應(yīng)繼承另一個(gè)。反之,賬戶對(duì)象的列表應(yīng)該自然地表現(xiàn)為銀行賬戶的實(shí)例屬性。
2.5.6 使用繼承我們通過將基類放置到類名稱后面的圓括號(hào)內(nèi)來(lái)指定繼承。首先,我們提供Account類的完整實(shí)現(xiàn),也包含類和方法的文檔字符串。
>>> class Account(object): """A bank account that has a non-negative balance.""" interest = 0.02 def __init__(self, account_holder): self.balance = 0 self.holder = account_holder def deposit(self, amount): """Increase the account balance by amount and return the new balance.""" self.balance = self.balance + amount return self.balance def withdraw(self, amount): """Decrease the account balance by amount and return the new balance.""" if amount > self.balance: return "Insufficient funds" self.balance = self.balance - amount return self.balance
CheckingAccount的完整實(shí)現(xiàn)在下面:
>>> class CheckingAccount(Account): """A bank account that charges for withdrawals.""" withdraw_charge = 1 interest = 0.01 def withdraw(self, amount): return Account.withdraw(self, amount + self.withdraw_charge)
這里,我們引入了類屬性withdraw_charge,它特定于CheckingAccount類。我們將一個(gè)更低的值賦給interest屬性。我們也定義了新的withdraw方法來(lái)覆蓋定義在Account對(duì)象中的行為。類語(yǔ)句組中沒有更多的語(yǔ)句,所有其它行為都從基類Account中繼承。
>>> checking = CheckingAccount("Sam") >>> checking.deposit(10) 10 >>> checking.withdraw(5) 4 >>> checking.interest 0.01
checking.deposit表達(dá)式是用于存款的綁定方法,它定義在Account類中,當(dāng) Python 解析點(diǎn)表達(dá)式中的名稱時(shí),實(shí)例上并沒有這個(gè)屬性,它會(huì)在類中查找該名稱。實(shí)際上,在類中“查找名稱”的行為會(huì)在原始對(duì)象的類的繼承鏈中的每個(gè)基類中查找。我們可以遞歸定義這個(gè)過程,為了在類中查找名稱:
如果類中有帶有這個(gè)名稱的屬性,返回屬性值。
否則,如果有基類的話,在基類中查找該名稱。
在deposit中,Python 會(huì)首先在實(shí)例中查找名稱,之后在CheckingAccount類中。最后,它會(huì)在Account中查找,這里是deposit定義的地方。根據(jù)我們對(duì)點(diǎn)運(yùn)算符的求值規(guī)則,由于deposit是在checking實(shí)例的類中查找到的函數(shù),點(diǎn)運(yùn)算符求值為綁定方法。這個(gè)方法以參數(shù)10調(diào)用,這會(huì)以綁定到checking對(duì)象的self和綁定到10的amount調(diào)用deposit方法。
對(duì)象的類會(huì)始終保持不變。即使deposit方法在Account類中找到,deposit以綁定到CheckingAccount實(shí)例的self調(diào)用,而不是Account的實(shí)例。
譯者注:CheckingAccount的實(shí)例也是Account的實(shí)例,這個(gè)說(shuō)法是有問題的。
調(diào)用祖先。被覆蓋的屬性仍然可以通過類對(duì)象來(lái)訪問。例如,我們可以通過以包含withdraw_charge的參數(shù)調(diào)用Account的withdraw方法,來(lái)實(shí)現(xiàn)CheckingAccount的withdraw方法。
要注意我們調(diào)用self.withdraw_charge而不是等價(jià)的CheckingAccount.withdraw_charge。前者的好處就是繼承自CheckingAccount的類可能會(huì)覆蓋支取費(fèi)用。如果是這樣的話,我們希望我們的withdraw實(shí)現(xiàn)使用新的值而不是舊的值。
2.5.7 多重繼承Python 支持子類從多個(gè)基類繼承屬性的概念,這是一種叫做多重繼承的語(yǔ)言特性。
假設(shè)我們從Account繼承了SavingsAccount,每次存錢的時(shí)候向客戶收取一筆小費(fèi)用。
>>> class SavingsAccount(Account): deposit_charge = 2 def deposit(self, amount): return Account.deposit(self, amount - self.deposit_charge)
之后,一個(gè)聰明的總經(jīng)理設(shè)想了AsSeenOnTVAccount,它擁有CheckingAccount和SavingsAccount的最佳特性:支取和存入的費(fèi)用,以及較低的利率。它將儲(chǔ)蓄賬戶和活期存款賬戶合二為一!“如果我們構(gòu)建了它”,總經(jīng)理解釋道,“一些人會(huì)注冊(cè)并支付所有這些費(fèi)用。甚至我們會(huì)給他們一美元。”
>>> class AsSeenOnTVAccount(CheckingAccount, SavingsAccount): def __init__(self, account_holder): self.holder = account_holder self.balance = 1 # A free dollar!
實(shí)際上,這個(gè)實(shí)現(xiàn)就完整了。存款和取款都需要費(fèi)用,使用了定義在CheckingAccount和SavingsAccount中的相應(yīng)函數(shù)。
>>> such_a_deal = AsSeenOnTVAccount("John") >>> such_a_deal.balance 1 >>> such_a_deal.deposit(20) # $2 fee from SavingsAccount.deposit 19 >>> such_a_deal.withdraw(5) # $1 fee from CheckingAccount.withdraw 13
就像預(yù)期那樣,沒有歧義的引用會(huì)正確解析:
>>> such_a_deal.deposit_charge 2 >>> such_a_deal.withdraw_charge 1
但是如果引用有歧義呢,比如withdraw方法的引用,它定義在Account和CheckingAccount中?下面的圖展示了AsSeenOnTVAccount類的繼承圖。每個(gè)箭頭都從子類指向基類。
對(duì)于像這樣的簡(jiǎn)單“菱形”,Python 從左到右解析名稱,之后向上。這個(gè)例子中,Python 按下列順序檢查名稱,直到找到了具有該名稱的屬性:
AsSeenOnTVAccount, CheckingAccount, SavingsAccount, Account, object
繼承順序的問題沒有正確的解法,因?yàn)槲覀兛赡軙?huì)給某個(gè)派生類高于其他類的優(yōu)先級(jí)。但是,任何支持多重繼承的編程語(yǔ)言必須始終選擇同一個(gè)順序,便于語(yǔ)言的用戶預(yù)測(cè)程序的行為。
擴(kuò)展閱讀。Python 使用一種叫做 C3 Method Resolution Ordering 的遞歸算法來(lái)解析名稱。任何類的方法解析順序都使用所有類上的mro方法來(lái)查詢。
>>> [c.__name__ for c in AsSeenOnTVAccount.mro()] ["AsSeenOnTVAccount", "CheckingAccount", "SavingsAccount", "Account", "object"]
這個(gè)用于查詢方法解析順序的算法并不是這門課的主題,但是 Python 的原作者使用一篇原文章的引用來(lái)描述它。
2.5.8 對(duì)象的作用Python 對(duì)象系統(tǒng)為使數(shù)據(jù)抽象和消息傳遞更加便捷和靈活而設(shè)計(jì)。類、方法、繼承和點(diǎn)運(yùn)算符的特化語(yǔ)法都可以讓我們?cè)诔绦蛑行纬蓪?duì)象隱喻,它能夠提升我們組織大型程序的能力。
特別是,我們希望我們的對(duì)象系統(tǒng)在不同層面上促進(jìn)關(guān)注分離。每個(gè)程序中的對(duì)象都封裝和管理程序狀態(tài)的一部分,每個(gè)類語(yǔ)句都定義了一些函數(shù),它們實(shí)現(xiàn)了程序總體邏輯的一部分。抽象界限強(qiáng)制了大型程序不同層面之間的邊界。
面向?qū)ο缶幊踢m合于對(duì)系統(tǒng)建模,這些系統(tǒng)擁有相互分離并交互的部分。例如,不同用戶在社交網(wǎng)絡(luò)中互動(dòng),不同角色在游戲中互動(dòng),以及不同圖形在物理模擬中互動(dòng)。在表現(xiàn)這種系統(tǒng)的時(shí)候,程序中的對(duì)象通常自然地映射為被建模系統(tǒng)中的對(duì)象,類用于表現(xiàn)它們的類型和關(guān)系。
另一方面,類可能不會(huì)提供用于實(shí)現(xiàn)特定的抽象的最佳機(jī)制。函數(shù)式抽象提供了更加自然的隱喻,用于表現(xiàn)輸入和輸出的關(guān)系。一個(gè)人不應(yīng)該強(qiáng)迫自己把程序中的每個(gè)細(xì)微的邏輯都塞到類里面,尤其是當(dāng)定義獨(dú)立函數(shù)來(lái)操作數(shù)據(jù)變得十分自然的時(shí)候。函數(shù)也強(qiáng)制了關(guān)注分離。
類似 Python 的多范式語(yǔ)言允許程序員為合適的問題匹配合適的范式。為了簡(jiǎn)化程序,或使程序模塊化,確定何時(shí)引入新的類,而不是新的函數(shù),是軟件工程中的重要設(shè)計(jì)技巧,這需要仔細(xì)關(guān)注。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/38139.html
摘要:對(duì)象表示信息,但是同時(shí)和它們所表示的抽象概念行為一致。通過綁定行為和信息,對(duì)象提供了可靠獨(dú)立的日期抽象。名稱來(lái)源于實(shí)數(shù)在中表示的方式浮點(diǎn)表示。另一方面,對(duì)象可以表示很大范圍內(nèi)的分?jǐn)?shù),但是不能表示所有有理數(shù)。 2.1 引言 來(lái)源:2.1 Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 在第一章中,我們專注于計(jì)算過程,以及程序設(shè)計(jì)中函數(shù)的作用。我們看到了...
摘要:以這種方式實(shí)現(xiàn)對(duì)象系統(tǒng)的目的是展示使用對(duì)象隱喻并不需要特殊的編程語(yǔ)言。我們的實(shí)現(xiàn)并不遵循類型系統(tǒng)的明確規(guī)定。反之,它為實(shí)現(xiàn)對(duì)象隱喻的核心功能而設(shè)計(jì)。是分發(fā)字典,它響應(yīng)消息和。 2.6 實(shí)現(xiàn)類和對(duì)象 來(lái)源:2.6 Implementing Classes and Objects 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 在使用面向?qū)ο缶幊谭妒綍r(shí),我們使用對(duì)象隱喻來(lái)指導(dǎo)程序...
摘要:為通用語(yǔ)言設(shè)計(jì)解釋器的想法可能令人畏懼。但是,典型的解釋器擁有簡(jiǎn)潔的通用結(jié)構(gòu)兩個(gè)可變的遞歸函數(shù),第一個(gè)求解環(huán)境中的表達(dá)式,第二個(gè)在參數(shù)上調(diào)用函數(shù)。這一章接下來(lái)的兩節(jié)專注于遞歸函數(shù)和數(shù)據(jù)結(jié)構(gòu),它們是理解解釋器設(shè)計(jì)的基礎(chǔ)。 3.1 引言 來(lái)源:3.1 Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 第一章和第二章描述了編程的兩個(gè)基本元素:數(shù)據(jù)和函數(shù)之間的...
摘要:我們是一個(gè)大型開源社區(qū),旗下群共余人,數(shù)量超過個(gè),網(wǎng)站日超過,擁有博客專家和簡(jiǎn)書程序員優(yōu)秀作者認(rèn)證。我們組織公益性的翻譯活動(dòng)學(xué)習(xí)活動(dòng)和比賽組隊(duì)活動(dòng),并和等國(guó)內(nèi)著名開源組織保持良好的合作關(guān)系。 Special Sponsors showImg(https://segmentfault.com/img/remote/1460000018907426?w=1760&h=200); 我們是一個(gè)...
閱讀 1534·2021-09-22 15:35
閱讀 2014·2021-09-14 18:04
閱讀 884·2019-08-30 15:55
閱讀 2456·2019-08-30 15:53
閱讀 2685·2019-08-30 12:45
閱讀 1208·2019-08-29 17:01
閱讀 2584·2019-08-29 15:30
閱讀 3521·2019-08-29 15:09