<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 第七章 更加抽象 > 來源:http://www.cnblogs.com/Marlowes/p/5426233.html > 作者:Marlowes 前幾章介紹了Python主要的內建對象類型(數字、字符串、列表、元組和字典),以及內建函數和標準庫的用法,還有定義函數的方法。現在看來,還差一點——創建自己的對象。這正是本章要介紹的內容。 為什么要自定義對象呢?建立自己的對象類型可能很酷,但是做什么用呢?使用字典、序列、數字和字符串來創建函數,完成這項工作還不夠嗎?這樣做當然可以,但是創建自己的對象(尤其是類型或者被稱為_類_的對象)是Python的核心概念——非常核心,事實上,Python被成為_面向對象_的語言(和SmallTalk、C++、Java以及其他語言一樣)。本章將會介紹如何創建對象,以及多態、封裝、方法、特性、超類以及繼承的概念——新知識很多。那么我們開始吧。 _注:熟悉面向對象程序設計概念的讀者也應該了解構造函數。本章不會提到構造函數,關于它的完整討論,請參見第九章。_ ## 7.1 對象的魔力 在面向對象程序設計中,術語_對象_(`object`)基本上可以看做數據(特性)以及由一系列可以存取、操作這些數據的方法所組成的集合。使用對像代替全局變量和函數的原因可能有很多。其中對象最重要的有點包括以下幾方面。 ? 多態(Polymorphism):意味著可以對不同類的對象使用同樣的操作,它們會像被“施了魔法一般”工作。 ? 封裝(Encapsulation):對外部世界隱藏對象的工作細節。 ? 繼承(Inheritance):以通用的類為基礎建立專門的對象。 在許多關于面向對象程序設計的介紹中,這幾個概念的順序是不同的。封裝和繼承會首先被介紹,因為它們被用作現實世界中的對象的模型。這種方法不錯,但是在我看來,面向對象程序設計最有趣的特性是多態。(以我的經歷來看)它也是讓大多數人犯暈的特性。所以本章會以多態開始,而且這一個概念就足以讓你喜歡面向對象程序設計了。 ### 7.1.1 多態 術語_多態_來自希臘語,意思是“有多種形式”。多態意味著就算不知道變量所引用的對象類型是什么,還是能對它進行操作,而它也會根據對象(或類)類型的不同而表現出不同的行為。例如,假設一個食品銷售的商業網站創建了一個在線支付系統。程序會從系統的其他部分(或者以后可能會設計的其他類似的系統)獲得一“購物車”中的商品,接下來要做的就是算出總價然后使用信用卡支付。 當你的程序獲得商品時,首先想到的可能是如何具體地表示它們。比如需要將它們作為元組接收,像下面這樣: ``` ("SPAM", 2.50) ``` 如果需要描述性標簽和價格,這樣就夠了。但是這個程序還是不夠靈活。我們假設網站支持拍賣服務,價格在貨物賣出之前會逐漸降低。如果用戶能夠把對象放入購物車,然后處理結賬(你的系統部分),等價格到了滿意的程度后按下“支付”按鈕就好了。 但是這樣一來簡單的元組就不能滿足需要了。為了實現這個功能,代碼每次詢問價格的時候,對象都需要檢查當前的價格(通過網絡的某些功能),價格不能固定在元組中。解決起來不難,只要寫個函數: ``` # Don't do it def getPrice(object): if isinstance(object, tuple): return object[1] else: return magic_network_method(object) ``` _注:這里用`isinstance`進行類型/類檢查是為了說明一點,類型檢查一般來說并不是什么好方法,能不用則不用。函數`isinstance`在7.2.6節會介紹。_ 前面的代碼中使用`isinstance`函數查看對象是否為元組。如果是的話,就返回它的第2個元素,否則會調用一些“有魔力的”網絡方法。 假設網絡功能部分已經存在,那么問題已經解決了,目前為止是這樣。但程序還不是很靈活。如果某些聰明的程序員決定用十六進制數的字符串來表示價格,然后存儲在字典中的鍵"price"下面呢?沒問題,只要更新函數: ``` # Don't do it def getPrice(object): if isinstance(object, tuple): return object[1] elif isinstance(object, dict): return int(objecct["price"]) else: return magic_network_method(object) ``` 現在是不是已經考慮到了所有的可能性?但是如果某些人希望為存儲在其他鍵下面的價格增加新的字典呢?那有怎么辦呢?可以再次更新`getPrice`函數,但是這種工作還要做多長時間?每次有人要實現價格對象的不同功能時,都要再次實現你的模塊。但是如果這個模塊已經賣出了并且轉到了其他更酷的項目中,那要怎么應付客戶?顯然這是個不靈活且不切實際的實現多種行為的代碼編寫方式。 那么應該怎么辦?可以讓對象自己進行操作。聽起來很清楚,但是想一下,這樣做會輕松很多。每個新的對象類型都可以檢索和計算自己的價格并且返回結果,只需向它詢問價格即可。這時候多態(在某種程度上還有封裝)就要出場了。 1\. 多態和方法 程序接收到一個對象,完全不了解該對象的內部實現方式——它可能有多種“形狀”。你要做的就是詢問價格,這樣就夠了,實現方法是我們熟悉的: ``` >>> object.getPrice() 2.5 ``` 綁定到對象特性上面的函數成為_方法_(method)。我們已經見過字符串、列表和字典方法。實際上多態也已經出現過: ``` >>> "abc".count("a") 1 >>> [1, 2, "a"].count("a") 1 ``` 對于變量`x`來說,不需要知道它是字符串還是列表,就可以調用它的`count`方法,不用管它是什么類型(只要你提供了一個字符串作為參數即可)。 讓我們做個實驗吧。標準庫`random`中包含`choice`函數,可以從序列中隨機選出元素。給變量賦值: ``` >>> from random import choice >>> x = choice(["Hello, world!", [1, 2, "e", "e", 4]]) ``` 運行后,變量`x`可能會包含字符串`"Hello, world!"`,也有可能包含列表`[1, 2, "e", "e", 4]`——不用關心到底是哪個類型。要關心的就是在變量`x`中字符`e`出現多少次,而不管`x`是字符串還是列表。可以使用剛才的`count`函數,結果如下: ``` >>> x.count("e") 1 ``` 本例中,看來是字符串勝出了(Marlowes:原文上隨機選擇到的是字符串。 =_=)。但是關鍵點在于不需要檢測類型:只需要知道`x`有個叫做`count`的方法,帶有一個字符作為參數,并且返回整數值就夠了。如果其他人創建的對象類也有`count`方法,那也無所謂,你只需要像用字符串和列表一樣使用該對象就行了。 2\. 多態的多種形式 任何不知道對象到底是什么類型,但是又要對對象“做點兒什么”的時候,都會用到多態。這不僅限于方法,很多內建運算符和函數都有多態的性質,考慮下面這個例子: ``` >>> 1 + 2 3 >>> "Fish" + "license" 'Fishlicense' ``` 這里的加運算符對于數字(本例中為整數)和字符串(以及其他類型的序列)都能起作用。為說明這一點,假設有個叫做`add`的函數,它可以將兩個對象相加。那么可以直接將其定義成上面的形式(功能等同但比`operator`模塊中的`add`函數效率低些)。 ``` >>> def add(x, y): ... return x + y # 對于很多類型的參數都可以用: >>> add(1, 2) 3 >>> add("Fish", "license") 'Fishlicense' ``` 看起來有些傻,但是關鍵在于參數可以是任何支持加法的對象(注意,這類對象只支持同類的加法。調用`add(1, "license")`不會起作用)。如果需要編寫打印對象長度消息的函數,只需要對象具有長度(`len`函數可用)即可。 ``` >>> def length_message(x): ... print "The length of", repr(x), "is", len(x) ``` 可以看到,函數中用了`repr`函數,`repr`函數是多態特性的代表之一,可以對任何東西使用。讓我們看看: ``` >>> length_message("Fnord") The length of 'Fnord' is 5 >>> length_message([1, 2, 3]) The length of [1, 2, 3] is 3 ``` 很多函數和運算符都是多態的——你寫的絕大多數程序可能都是,即便你并非有意這樣。只要使用多態函數和運算符,就會與“多態”發生關聯。事實上,唯一能夠毀掉多態的就是使用函數顯式地檢查類型,比如`type`、`isinstance`以及`issubclass`函數等。如果可能的話,應該盡力避免使用這些毀掉多態的方式。真正重要的是如何讓對象按照你所希望的方式工作,不管它是否是正確的類型(或者類)。 _注:這里所討論的多態的形式是Python式編程的核心,也是被成為“鴨子類型”(duck typing)的東西。這個名詞出自俗語“如果它像鴨子一樣呱呱大叫······”。有關它的更多信息,請參見 http://en.wikipedia.org/wiki/Duck_typing _ ### 7.1.2 封裝 _封裝_是指向程序中的其他部分隱藏對象的具體實現的原則。聽起來有些像多態,也是使用對象而不用知道其內部細節,兩者概念有些類似,因為它們都是_抽象的原則_,它們都會幫助處理程序組件而不用過多關心多余細節,就像函數做的一樣。 但是封裝并不等同于多態。多態可以讓用戶對于不知道是什么類(對象類型)的對象進行方法調用,而封裝是可以不用關心對象是如何構建的而直接進行調用。聽起來還是有些相似?讓我們用多態而不用封裝寫個例子,假設有個叫做`OpenObject`的類(本章后面會學到如何創建類): ``` >>> o = OpenObject() # This is how we create objects... >>> o.setName("Sir Lancelot") >>> o.getName() 'Sir Lancelot' ``` 創建了一個對象(通過像調用函數一樣調用類)后,將變量`o`綁定到該對象上。可以使用`setName`和`getName`方法(假設已經由`OpenObject`類提供)。一切看起來都很完美。但是假設變量`o`將它的名字存儲在全局變量`globalName`中: ``` >>> globalName "Sir Lancelot" ``` 這就意味著在使用`OpenObject`類的實例時候,不得不關心`globalName`的內容。實際上要確保不會對它進行任何更改: ``` >>> globalName = "Sir XuHoo" >>> o.getName() 'Sir XuHoo' ``` 如果創建了多個`OpenObject`實例的話就會出現問題,因為變量相同,所以可能會混淆: ``` >>> o1 = OpenObject() >>> o2 = OpenObject() >>> o1.setName("Robin Hood") >>> o2.getName() 'Robin Hood' ``` 可以看到,設定一個名字后,其他的名字也就自動設定了。這可不是想要的結果。 基本上,需要將對象進行抽象,調用方法的時候不用關心其他的東西,比如它是否干擾了全局變量。所以能將名字“封裝”在對象內嗎?沒問題。可以將其作為_特性_(attribute)存儲。 正如方法一樣,特性是作為變量構成對象的一部分,事實上方法更像是綁定到函數上的屬性(在本章的7.2.3節中會看到方法和函數重要的不同點)。 如果不用全局變量而用特性重寫類,并且重命名為`ClosedObject`,它會像下面這樣工作: ``` >>> c = ClosedObject() >>> c.setName("Sir Lancelot") >>> c.getName() 'Sir Lancelot' ``` 目前為止還不錯。但是,值可能還是存儲在全局變量中的。那么再創建另一個對象: ``` >>> r = ClosedObject() >>> r.setName("Sir Robin") >>> r.getName() 'Sir Robin' ``` 可以看到新的對象的名稱已經正確設置。這可能正是我們期望的。但是第一個對象怎么樣了呢? ``` >>> c.getName() 'Sir Lancelot' ``` 名字還在!這是因為對象有它自己的_狀態_(state)。對象的狀態由它的特性(比如名稱)來描述。對象的方法可以改變它的特性。所以就像是將一大堆函數(方法)捆在一起,并且給予它們訪問變量(特性)的權力,它們可以在函數調用之間保持保存的值。 本章后面的“再論私有化”一節也會對Python的封裝機制進行更詳細的介紹。 ### 7.1.3 繼承 繼承是另外一個懶惰(褒義)的行為。程序員不想把同一段代碼輸入好幾次。之前使用的函數避免了這種情況,但是現在又有個更微妙的問題。如果已經有了一個類,而又想建立一個非常類似的呢?新的類可能只是添加幾個方法。在編寫新類時,又不想把舊類的代碼全都復制過去。 比如說有個`Shape`類,可以用來在屏幕上畫出指定的形狀。現在需要創建一個叫做`Rectangle`的類,它不但可以在屏幕上畫出指定的形狀,而且還能計算該形狀的面積。但又不想把Shape里面已經寫好的`draw`方法再寫一次。那么該怎么辦?可以讓`Rectangle`從`Shape`類_繼承_方法。在`Rectangle`對象上調用`draw`方法時,程序會自動從`Shape`類調用該方法。(參見7.2.5節)。 ## 7.2 類和類型 現在讀者可能對什么是類有了大體感覺——或者已經有些不耐煩聽我對它進行更多介紹了。在開始介紹之前,先來認識一下什么是類,以及它和類型又有什么不同(或相同)。 ### 7.2.1 類到底是什么 前面的部分中,類這個詞已經多次出現,可以將它或多或少地視為_種類_或者_類型_的同義詞。從很多方面來說,這就是類——一種對象。所有的對象都屬于某一個類,稱為類的_實例_(instance)。 例如,現在請往窗外看,鳥就是“鳥類” 的實例。鳥類是一個非常通用(抽象)的類,具有很多子類:看到的鳥可能屬于子類“百靈鳥”。可以將“鳥類”想象成所有鳥的集合,而“百靈鳥類”是其中的一個子集。當一個對象所屬的類是另外一個對象所屬類的子集時,前者就被成為后者的_子類_(subclass),所以“百靈鳥類”是“鳥類”的子類。相反,“鳥類”是“百靈鳥類”的_超類_(superclass)。 _注:日常交談中,可能經常用復數來描述對象的類,比如`birds`或者`larkes`。Python中,習慣上都使用單數名詞,并且首字母大寫,比如`Bird`和`Lark`。_ 這樣一比喻,子類和超類就容易理解了。但是在面向對象程序設計中,子類的關系是隱式的,因為一個類的定義取決于它所支持的方法。類的所有實例都會包含這些方法,所以所有_子類_的所有實例都有這些方法。定義子類只是個定義更多(也有可能是重載已經存在的)的方法的過程。 例如,鳥類Bird可能支持fly方法,而企鵝類`Penguin`(`Bird`的子類)可能會增加個`eatFish`方法。當創建`Penguin`類時,可能會想要重寫(`override`)超類的`fly`方法,對于`Penguin`的實例來說,這個方法要么什么也不做,要么就產生異常(參見第8章),因為`penguin`(企鵝)不會`fly`(飛)。 _注:在舊版本的Python中,類和類型之間有很明顯的區別。內建的對象是基于類型的,自定義的對象則是基于類的。可以創建類但是不能創建類型。最近版本的Python中,事情有了些變化。基本類型和類之間的界限開始模糊了。可以創建內建類型的子類(或子類型),而這些類型的行為更類似于類。在越來越熟悉這門語言后會注意到這一點。如果感興趣的話,第九章中會有關于這方面的更多信息。_ ### 7.2.2 創建自己的類 終于來了!可以創建自己的類了!先來看一個簡單的類: ``` # 確定使用新式類 __metaclass__ = type class Person: def setName(self, name): self.name = Name def getName(self): return self.name def greet(self): print "Hello, world! I'm %s" % self.name ``` _注:所謂的舊式類和新式類之間是有區別的。除非是Python3.0之前版本中默認附帶的代碼,否則再繼續使用舊式類已無必要。新式類的語法中,需要在模塊或者腳本開始的地方放置賦值語句`__metaclass__ = type`(并不會在每個例子中顯式地包含這行語句)。除此之外也有其他的方法,例如繼承新式類(比如`object`)。后面馬上就會介紹繼承的知識。在Python3.0中,舊式類的問題不用再擔心,因為它們根本就不存在了。請參見第九章獲取更多信息。_ 這個例子包含3個方法定義,除了它們是寫在`class`語句里面外,一切都像是函數定義。`Person`當然是類的名字。`class`語句會在函數定義的地方創建自己的命名空間(參見7.2.4節)。一切看起來都挺好,但是那個`self`參數看起來有點奇怪。它是對于對象自身的引用。那么它是什么對象?讓我們創建一些實例看看: ``` >>> foo = Person() >>> bar = Person() >>> foo.setName("Luke Skywalker") >>> bar.setName("Anakin Skywalker") >>> foo.greet() Hello, world! I'm Luke Skywalker >>> bar.greet() Hello, world! I'm Anakin Skywalker ``` 好了,例子一目了然,應該能說明`self`的用處了。在調用`foo`的`setName`和`greet`函數時,`foo`自動將自己作為第一個參數傳入函數中——因此形象的命名為`self`。對于這個變量,每個人可能都會有自己的叫法,但是因為它總是對象自身,所以習慣上總是叫做`self`。 顯然這就是`self`的用處和存在的必要性。沒有它的話,成員方法就沒法訪問他們要對其特性進行操作的對象本身了。 和之前一樣,特性是可以在外部訪問的: ``` >>> foo.name 'Luke Skywalker' >>> bar.name = "Yoda" >>> bar.greet() Hello, world! I'm Yoda ``` _注:如果知道`foo`是`Person`的實例的話,那么還可以把`foo.greet()`看作`Person.greet(foo)`方便的簡寫。_ ### 7.2.3 特性、函數和方法 (在前面提到的)`self`參數事實上正是方法和函數的區別。方法(更專業一點可以成為_綁定_方法)將它們的第一個參數綁定到所屬的實例上,因此您無需顯式提供該參數。當然也可以將特性綁定到一個普通函數上,這樣就不會有特殊的`self`參數了: ``` >>> class Class: ... def method(self): ... print "I have a self!" >>> def function(): ... print "I don't..." >>> instance = Class() >>> instance.method() I have a self! >>> instance.method = function >>> instance.method() I don't... ``` 注意,`self`參數并不依賴于調用方法的方式,前面我們使用的是`instance.method`(實例.方法)的形式,可以隨意使用其他變量引用同一個方法: ``` >>> class Bird: ... song = "Squaawk!" ... def sing(self): ... print self.song >>> bird = Bird() >>> bird.sing() Squaawk! >>> brid.song >>> birdsong = bird.sing() Squaawk! ``` 盡管最后一個方法調用起來與函數調用十分相似,但是變量`birdsong`引用綁定方法(第九章中,將會介紹類是如何調用超類方法的(具體來說就是超類的構造器)。這些方法直接通過類調用,他們沒有綁定自己的`self`參數到任何東西上,所以叫做非綁定方法)`bird.sing`上,也就意味著這還是會對`self`參數進行訪問(也就是說,它仍舊綁定到類的相同實例上)。 **再論私有化** 默認情況下,程序可以從外部訪問一個對象的特性。再次使用前面討論過的相關封裝的例子: ``` >>> c.name 'Sir Lancelot' >>> c.name = 'Sir Gumby' >>> c.getName() 'Sir Gumby' ``` 有些程序員覺得這樣做是可以的,但是有些人(比如SmallTalk之父,SmallTalk的對象特性只允許由同一個對象的方法訪問)覺得這樣做就破壞了封裝的原則。他們認為對象的狀態對于外部應該是_完全隱藏_(不可訪問)的。有人可能會奇怪為什么他們會站在如此極端的立場上。每個對象管理自己的特性還不夠嗎?為什么還要對外部世界隱藏呢?畢竟如果能直接使用`ClosedObject`的`name`特性的話就不用使用`setName`和`getName`方法了。 關鍵在于其他程序員可能不知道(可能也不應該知道)你的對象內部的具體操作。例如,`ClosedObject`可能會在其他對象更改自己的名字的時候,給一些管理員發送郵件消息。這應該是`setName`方法的一部分。但是如果直接使用`c.name`設定名字會發生什么?什么都沒發生,Email也沒發出去。為了避免這類事情的發生,應該使用_私有_(private)特性,這是外部對象無法訪問,但`getName`和`setName`等_訪問器_(accessor)能夠訪問的特性。 _注:第九章中,將會介紹有關屬性(property)的只是,它是訪問器最有力的替代者。_ Python并不直接支持私有方式,而是要靠程序員自己把握在外部進行特性修改的時機。畢竟在使用對象前應該知道如何使用。但是,可以用一些小技巧達到私有特性的效果。 為了讓方法或者特性變為私有(從外部無法訪問),只要在它的名字前面加上雙下劃線即可: ``` class Secretive(): def __inaccessible(self): print "Bet you can't see me..." def accessible(self): print "The secret message is:" self.__inaccessible() ``` 現在`__inaccessible`從外界是無法訪問的,而在類內部還能使用(比如從`accessible`)訪問: ``` >>> s = Secretive() >>> s.__inaccessible() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: Secretive instance has no attribute '__inaccessible' >>> s.accessible() The secret message is: Bet you can't see me... ``` 盡管雙下劃線有些奇怪,但是看起來像是其他語言中的標準的私有方法。真正發生的事情才是不標準的。類的內部定義中,所有以雙下劃線開始的名字都被“翻譯”成前面加上單下劃線和類名的形式: ``` >>> Secretive._Secretive__inaccessible <unbound method Secretive.__inaccessible> ``` 在了解這些幕后的事情后,實際上還能在類外訪問這些私有方法,盡管不應該這么做: ``` >>> s._Secretive__inaccessible() Bet you can't see me... ``` 簡而言之,確保其他人不會訪問對象的方法和特性是不可能的,但是這類”名稱變化術“是他們不應該訪問這些函數或者特性的強有力信號。 如果不需要使用這種方法但是又想讓其他對象不要訪問內部數據,那么可以使用單下劃線。這不過是個習慣,但的確有實際效果。例如,前面有下劃線的名字都不會被帶星號的import語句(`from module import *`)導入(有些語言支持多層次的成員變量(特性)私有性。比如Java就支持4種級別。盡管單下劃線在某種程度上給出兩個級別的私有性,但Python并沒有真正的私有化支持)。 ### 7.2.4 類的命名空間 下面的兩個語句(幾乎)等價: ``` def foo(x): return x * x foo = lambda x: x * x ``` 兩者都創建了返回參數平方的函數,而且都將變量`foo`綁定到函數上。變量`foo`可以在全局(模塊)范圍進行定義,也可處于局部的函數或方法內。定義類時,同樣的事情也會發生,所有位于`class`語句中的代碼都在特殊的命名空間中執行——類命名空間(`class namespace`)。這個命名空間可由類內所有成員訪問。并不是所有Python程序員都知道類的定義其實就是執行代碼塊,這一點非常有用,比如,在類的定義區并不只限定只能使用self語句: ``` >>> class C: ... print "Class C being defined..." ... Class C being defined... >>> ``` 看起來有點傻,但是看看下面的: ``` >>> class MemberCounter: ... members = 0 ... def init(self): ... MemberCounter.members += 1 ... >>> m1 = MemberCounter() >>> m1.init() >>> MemberCounter.members 1 >>> m2 = MemberCounter() >>> m2.init() >>> MemberCounter.members 2 ``` 上面的代碼中,在類作用域內定義了一個可供所有成員(實例)訪問的變量,用來計算類的成員數量。注意`init`用來初始化所有實例:第九章中,我會讓這一過程自動化(即把它變成一個適當的構造函數)。 就像方法一樣,類作用域內的變量也可以被所有實例訪問: ``` >>> m1.members 2 >>> m2.members 2 ``` 那么在實例中重綁定`members`特性呢? ``` >>> m1.members = "Two" >>> m1.members 'Two' >>> m2.members 2 ``` 新`members`值被寫到了`m1`的特性中,屏蔽了類范圍內的變量。這跟函數內的局部和全局變量的行為十分類似,就像第六章討論的”屏蔽的問題“。 ### 7.2.5 指定超類 就像本章前面我們討論的一樣,子類可以擴展超類的定義。將其他類名寫在`class`語句后的圓括號內可以指定超類: ``` class Filter(): def init(self): self.blocked = [] def filter(self, sequence): return [x for x in sequence if x not in self.blocked] class SPAMFilter(Filter): # SPAMFilter是Filter的子類 def init(self): # 重寫Filter超類中的init方法 self.blocked = ["SPAM"] ``` `Filter`是個用于過濾序列的通用類,事實上它不能過濾任何東西: ``` >>> f = Filter() >>> f.init() >>> f.filter([1, 2 ,3]) [1, 2, 3] ``` `Filter`類的用處在于它可以用作其他類的基類(超類),比如`SPAMFilter`類,可以將序列中的`'SPAM'`過濾出去。 ``` >>> s = SPAMFilter() >>> s.init() >>> s.filter(["SPAM", "SPAM", "SPAM", "SPAM", "eggs", "bacon", "SPAM"]) ['eggs', 'bacon'] ``` 注意`SPAMFilter`定義的兩個要點。 ? 這里用提供新定義的方式重寫了`Filter`的`init`定義。 ? `filter`方法的定義是從`Filter`類中拿過來(繼承)的,所以不用重寫它的定義。 第二個要點揭示了繼承的用處:我可以寫一大堆不同的過濾類,全部都從`Filter`繼承,每一個我都可以使用已經實現的`filter`方法。這就是前面提到過的有用的懶惰。 ### 7.2.6 檢查繼承 如果想要查看一個類是否是另一個的子類,可以使用內建的`issubclass`函數: ``` >>> issubclass(SPAMFilter, Filter) True >>> issubclass(Filter, SPAMFilter) False ``` 如果想要知道已知的基類(們),可以直接使用它的特殊特性`__bases__`。 ``` >>> SPAMFilter.__bases__ (<class __main__.Filter at 0x7fa160e4a4c8>,) >>> Filter.__bases__ () ``` 同樣,還能用使用`isinstance`方法檢查一個對象是否是一個類的實例: ``` >>> s = SPAMFilter() >>> isinstance(s, SPAMFilter) True >>> isinstance(s, Filter) True >>> isinstance(s, str) False ``` _注:使用`isinstance`并不是個好習慣,使用多態會更好一些。_ 可以看到,s是`SPAMFilter`類的(直接)實例,但是它也是`Filter`類的間接實例,因為`SPAMFilter`是`Filter`的子類。另外一種說法就是`SPAMFilter`類就是`Filters`類。可以從前一個例子中看到,`isinstance`對于類型也起作用,比如字符串類型(`str`)。 如果只想知道一個對象屬于哪個類,可以使用`__class__`特性: ``` >>> s.__class__ <class __main__.SPAMFilter at 0x7fa160e4a530> ``` _注:如果使用`__metaclass__ = type`或從`object`繼承的方式來定義新式類,那么可以使用`type(s)`查看實例所屬的類。_ ### 7.2.7 多個超類 可能有的讀者注意到了上一節中的代碼有些奇怪:也就是`__bases__`這個復數形式。而且文中也提到過可以找到一個新的基類(們),也就按暗示它的基類可能會多余一個。事實上就是這樣,建立幾個新的類來試試看: ``` class Calculator: def calculate(self, expression): self.value = eval(expression) class Talker: def talk(self): print "Hi, my value is", self.value class TalkingCalculator(Calculator, Talker): pass ``` 子類(`TalkingCalculator`)自己不做任何事,它從自己的超類繼承所有的行為。它從`Calculator`類那里繼承`calculate`方法,從`Talker`類那里繼承`talk`方法,這樣它就成了會說話的計算器(talking calculator)。 ``` >>> tc = TalkingCalculator() >>> tc.calculate("1 + 2 * 3") >>> tc.talk() Hi, my value is 7 ``` 這種行為稱為_多重繼承_(multiple inheritance),是個非常有用的工具。但除非讀者特別熟悉多重繼承,否則應該盡量避免使用,因為有些時候會出現不可預見的麻煩。 當使用多重繼承時,有個需要注意的地方。如果一個方法從多個超類繼承(也就是說你有兩個具有相同名字的不同方法),那么必須要注意一下超類的順序(在`class`語句中):先繼承的類中的方法會重寫后繼承的類中的方法。所以如果前例中`Calculator`類也有個叫做`talk`的方法,那么它就會重寫`Talker`的`talk`方法(使其不可訪問)。如果把它們的順序調過來,像下面這樣: ``` class TalkingCalculator(Talker, Calculator): pass ``` 就會讓`Talker`的`talk`方法可用了。如果超類們共享一個超類,那么在查找給定方法或者屬性時訪問超的順序稱為MRO(Method Resolution Order, 方法判定順序),使用的算法相當復雜。幸好,它工作得很好,所以不用過多關心。 ### 7.2.8 接口與內省 “接口”的概念與多態有關。在處理多態對象時,只要關心它的接口(或稱“協議”)即可,也就是公開的方法和特性。在Python中,不用顯式地指定對象必須包含哪些方法才能作為參數接收。例如,不用(像在Java中一樣)顯式地編寫接口,可以在使用對象的時候假定它可以實現你所要求的行為。如果它不能實現的話,程序就會失敗。 一般來說只需要讓對象符合當前的接口(換句話說就是實現當前方法),但是還可以更靈活一些。除了調用方法然后期待一切順利之外,還可檢查所需方法是否已經存在。如果不存在,就需要做些其他事情: ``` >>> hasattr(tc, "talk") True >>> hasattr(tc, "fnord") False ``` _注:`callable`函數在Python3.0中已不再可用。可以使用`hasattr(x, "__call__")`來代替`callable(x)`。_ 這段代碼使用了`getattr`函數,而沒有在`if`語句內使用`hasattr`函數直接訪問特性,`getattr`函數允許提供默認值(本例中為`None`),以便在特性不存在時使用,然后對返回的對象使用`callable`函數。 _注:與`getattr`相對應的函數是`setattr`,可以用來設置對象的特性:_ ``` >>> setattr(tc, "name", "Mr. XuHoo") >>> tc.name 'Mr. XuHoo' ``` 如果要查看對象內所有存儲的值,那么可以使用`__dict__`特性。如果真的想要找到對象是由什么組成的,可以看看`inspect`模塊。這是為那些想要編寫對象瀏覽器(以圖形方式瀏覽Python對象的程序)以及其他需要類似功能的程序的高級用戶準備的。關于對象和模塊的更多信息,可以參見10.2節。 ## 7.3 一些關于面向對象設計的思考 關于面向對象設計的書籍已經有很多,盡管這并不是本書所關注的主題,但是還是給出一些要點。 ? 將屬于一類的對象放在一起。如果一個函數操縱一個全局變量,那么兩者最好都在類內作為特性和方法出現。 ? 不要讓對象過于親密。方法應該只關心自己實例的特性。讓其他實例管理自己的狀態。 ? 要小心繼承,尤其是多重繼承。繼承機制有時很有用,但也會在某些情況下讓事情變得過于復雜。多繼承難以正確使用,更加難以調試。 ? 簡單就好。讓你的方法小巧。一般來說,多數方法都應能在30秒內被讀完(以及理解),盡量將代碼行數控制在一頁或者一屏之內。 當考慮需要什么類以及類要有什么方法時,應該嘗試下面的方法。 (1)寫下問題的描述(程序要做什么),把所有的名詞、動詞和形容詞加下劃線。 (2)對于所有名詞,用作可能的類。 (3)對于所有動詞,用作可能的方法。 (4)對于所有形容詞,用作可能的特性。 (5)把所有方法和特性分配到類。 現在已經有了_面向對象模型_的草圖了。還可以考慮類和對象之間的關系(比如繼承或協作)以及它們的作用,可以用以下步驟精煉模型。 (1)寫下(或者想象)一系列的_使用實例_,也就是程序應用時的場景,試著包括所有的功能。 (2)一步步考慮每個使用實例,保證模型包括所有需要的東西。如果有些遺漏的話就添加進來。如果某處不太正確則改正。繼續,直到滿意為止, 當認為已經有了可以應用的模型時,那就可以開工了。可能需要修正自己的模型,或者是程序的一部分。幸好,在Python中不用過多關心這方面的事情,因為很簡單,只要投入進去就行(如果需要面向對象程序設計方面的更多指導,請參見第十九章推薦的書目)。 ## 7.4 小結 本章不僅介紹了更多關于Python語言的信息,并且介紹了幾個可能完全陌生的概念。下面總結一下。 ? 對象:對象包括特性和方法。特性只是作為對象的一部分變量,方法則是存儲在對象內的函數。(綁定)方法和其他函數的區別在于方法總是將對象作為自己的第一個參數,這個參數一般稱為self。 ? 類:類代表對象的集合(或一類對象),每個對象(實例)都有一個類。類的主要任務是定義它的實例會用到的方法。 ? 多態:多態是實現將不同類型和類的對象進行同樣對待的特性——不需要知道對象屬于哪個類就能調用方法。 ? 封裝:對象可以將它們的內部狀態隱藏(或封裝)起來。在一些語言中,這意味著對象的狀態(特性)只對自己的方法可用。在Python中,所有的特性都是公開可用的,但是程序員應該在直接訪問對象狀態時謹慎行事,因為他們可能無意中使得這些特性在某些方面不一致。 ? 繼承:一個類可以是一個或者多個類的子類。子類從超類繼承所有方法。可以使用多個超類,這個特性可以用來組成功能的正交部分(沒有任何聯系)。普通的實現方式是使用核心的超類和一個或者多個混合的超類。 ? 接口和內省:一般來說,對于對象不用探討過深。程序員可以靠多態調用自己需要的方法。不過如果想要知道對象到底有什么方法和特性,有些函數可以幫助完成這項工作。 ? 面向對象設計:關于如何(或者說是否應該進行)面向對象設計有很多的觀點。不管你持什么觀點,完全理解這個問題,并且創建容易理解的設計是很重要的。 ### 7.4.1 本章的新函數 本章涉及的新函數如表7-1所示。 **表7-1 本章的新函數** ``` callable(object) 確定對象是否可調用(比如函數或者方法) getattr(object, name[ ,default]) 確定特性的值,可選擇提供默認值 hasattr(object, name) 確定對象是否有給定的特性 isinstance(object, class) 確定對象是否是類的實例 issubclass(A, B) 確定A是否為B的子類 random.choice(sequence) 從非空序列中隨機選擇元素 setattr(object, name, value) 設定對象的給定特性為value type(object) 返回對象的類型 ``` ### 7.4.2 接下來學什么 前面已經介紹了許多關于創建自己的對象以及自定義對象的作用。在輕率地進軍Python特殊方法的魔法陣(第九章)之前,讓我們先喘口氣,看看介紹異常處理的簡短的一章。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看