2020年10月9日星期五

008 零基礎學的Python 第一篇 Python語言基礎 第8章 物件導向程式設計

 申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!


8章 物件導向程式設計

物件導向的程式設計提供了一種新的思維方式,軟體設計的焦點不再是程式的邏輯流程,而是軟體或程式中的物件以及物件之間的關係。使用物件導向的思想進行程式設計,能夠更好地設計軟體架構,維護軟體模組,並易於框架和組件的重用。

Python支援面向過程、物件導向、函數式程式設計等多種程式設計範式。Python不強制我們使用任何一種程式設計範式,我們可以使用過程式程式設計編寫任何程式,在編寫小程式(少於500行代碼)時,基本上不會有問題。但對於中等和大型項目來說,物件導向將給我們帶來很多優勢。本章將結合物件導向的基本概念和Python語法的特性講解物件導向的程式設計。

本章的知識點:

·類和對象

·屬性和方法

·繼承和組合

·類的多態性

·類的存取權限

·設計模式的應用

 

8.1 物件導向的概述

物件導向是一種方法學。物件導向是一種描述業務問題、設計業務實體和實體之間關係的方法。物件導向技術已經成為當今軟體設計和開發領域的主流技術。物件導向主要用於軟體發展的分析和設計階段,通常使用UML(統一模組化語言)進行建模。

統一模組化語言並不是軟體設計開發的方法,而是一種描述軟體發展過程的圖形化標記。UML使用若干種模型圖來描述軟體發展中的每個重要步驟。

1.用例圖

用例圖描述系統使用者與系統之間的交互關係。用例圖通常用於系統的分析階段,分析系統中主要的流程。用例圖用於描述不同的業務實體,以及實體之間的關係。

2.活動圖

活動圖是對用例圖的補充,用於分析複雜的用例,表示用例中的邏輯行為。活動圖類似于傳統的資料流程圖,可以描述業務的流程,説明分析人員確定用例中的交互及其關係。

3.狀態圖

狀態圖用於對系統的行為進行建模,用於分析和設計階段之間的過渡時期。狀態圖和活動圖有些相似,狀態圖是對單個物件的描述,強調物件內部狀態的變遷過程。

4.類圖

類圖包括屬性、操作以及存取權限等內容。類圖用於系統設計階段,根據系統用例提煉出系統中的物件。類圖是物件導向設計的核心,通過類圖可以表示抽象、繼承、多態等物件導向特性。能夠表現系統的架構設計及系統中物件之間的層次關係。

5.序列圖和協助圖

序列圖和協助圖都可以用於描述系統中物件之間的行為,是對系統具體行為的設計。序列圖和協助圖通常用於類圖設計完成後的一個步驟。序列圖是對用例的具體實現,通過消息的方式描述系統中物件的生命週期和控制流程;而協助圖側重於對消息的建模,描述系統物件之間的交互關係。

6.組件圖和部署圖

元件圖用於描述系統元件之間的交互,類和元件之間的依賴關係。部署圖用於描述系統的部署及元件之間的配置。

這些圖形標記都可以使用UML建模工具繪製,例如,visioRational Rose。使用這些建模工具可以準確、快速地描述系統的行為。visioOffice系列的工具之一,使用簡單,容易上手。而Rational Rose功能強大,具有通過模型生成各種語言的代碼,以及對代碼更新來同步模型等特性。使用Rational Rose能更好地管理物件,但是Rational Rose軟體龐大,必須學習每種圖形的繪製方法。建模工具只是輔助系統分析、設計的手段,系統分析人員甚至可以使用手寫的方式來描述系統的模型,重要的是能夠清晰、準確地分析和設計,而不是軟體工具的優劣。

 

8.2 類和對象

在物件導向的設計中,程式師可以創建任何新的類型,這些類型可以描述每個物件包含的資料和特徵,這種類型稱為類。類是一些物件的抽象,隱藏了物件內部複雜的結構和實現。類由變數和函數兩部分構成,類中的變數稱為成員變數,類中的函數稱為成員函數。

8.2.1 類和對象的區別

類和物件是物件導向中的兩個重要概念,初學者經常把類和物件混為一談。類是對客觀世界中事物的抽象,而物件是類產生實體後的實體。例如,同樣的汽車模型可以製造出不同的汽車,每輛汽車就是一個物件,汽車模型則為一個類。車牌號可以標識每輛汽車,不同的汽車有不同的顏色和價格,因此車牌號、顏色、價格是汽車的屬性。圖8-1描述了類和物件的關係。


8-1 類和物件的關係

汽車模型是對汽車特徵和行為的抽象,而汽車是實際存在的事物,是客觀世界中實實在在的實體。因此,根據類的定義可以構造出許多物件。現實生活中可以看到很多這樣的例子,如,按照零件模型可以製造出多個零件,按照施工圖紙可以建造出多棟樓房。

8.2.2 類的定義

Python使用class關鍵字定義一個類,類名的首字元一般要大寫。當程式師需要創建的類型不能用簡單類型來表示時,則需要定義類,然後利用定義的類創建物件。類把需要使用的變數和方法組合在一起,這種方式稱為封裝。由於歷史原因,我們會發現定義類有以下兩種方式。


01      # 繼承自object

02      class Class_name(object):

03         

04      # 不顯式繼承object

05      class Class_name :

06         


這是因為在Python2.2版本之前,typeobject還沒有統一。使用class Class_name(object)來聲明的類稱為新式類,它的type<type'type'>。而使用class Class_name來聲明的類,其type<type'classobj'>。而到了3.Xobject已經默認作為所有類的基類,所以這兩種寫法就沒有區別了,讀者可以選擇自己喜歡的風格使用。感興趣的讀者可以使用type(Class_name)查看輸出的結果。

注意  本書中採用第二種聲明方法。

類必須用關鍵字class定義,關鍵字class後面是類名。類的主體由一系列的屬性和方法組成。下面這段代碼創建了一個簡單的水果類,水果具有成長的行為,因此定義了1個方法grow()


01      # 類的創建

02      class Fruit::

03          def __init__(self):                 # __init__為類的構造函數,後面會詳細介紹

04              self.name = name

05              self.color = color

06          def grow(self):                     # 定義grow函數,類中的函數稱為方法

07              print "Fruit grow ..."


【代碼說明】 2行代碼定義了一個名為Fruit的類。第3行是構造函數,定義了名稱和顏色兩個屬性。第6行在類Fruit中創建了1個函數grow()。類的函數至少有1個參數selfself參數的含義會在後面的內容中介紹。

注意  類的方法必須有1self參數。但是在方法調用時,可以不傳遞這個參數。

8.2.3 對象的創建

創建物件的過程稱為產生實體。當一個物件被創建後,包含3個方面的特性:物件的控制碼、屬性和方法。物件的控制碼用於區分不同的物件,當物件被創建後,該物件會獲取一塊存儲空間,存儲空間的位址即為物件的標識。物件的屬性和方法與類的成員變數和成員函數相對應。


01      if __name__ == "__main__":

02          fruit = Fruit()               # 產生實體

03          fruit.grow()                # 調用grow()方法


【代碼說明】 2行代碼對Fruit類進行產生實體,Python的產生實體與Java不同,並不需要new關鍵字。Python的產生實體與函數的調用相似,使用類名加圓括號的方式。第3行代碼,調用Fruit類的grow()方法。輸出結果:


Fruit grow ...


8.3 屬性和方法

類由屬性和方法組成。類的屬性是對資料的封裝,而類的方法則表示物件具有的行為。類通常由函數(實例方法)和變數(類變數)組成。Python的構造函數、析構函數、私有屬性或方法都是通過名稱約定區分的。此外,Python還提供了一些有用的內置方法,簡化了類的實現。

8.3.1 類的屬性

類的屬性一般分為私有屬性和公有屬性,如C++C#Java等物件導向的語言都有定義私有屬性的關鍵字。而Python中沒有這類關鍵字,預設情況下所有的屬性都是公有的,這樣對類中屬性的訪問將沒有任何限制,並且都會被子類繼承,也能從子類中進行訪問。這肯定不是我們想要的。Python使用約定屬性名稱來達到這樣資料封裝的目的。如果屬性的名字以兩個底線開始,就表示為私有屬性;反之,沒有使用雙底線開始的表示公有屬性。類的方法也同樣使用這樣的約定。

注意  Python沒有保護類型的修飾符。

Python的屬性分為實例屬性和靜態屬性。實例屬性是以self作為首碼的屬性。__init__方法即Python類的構造函數,詳細用法請參考8.3.4小節。如果__init__方法中定義的變數沒有使用self作為首碼聲明,則該變數只是普通的區域變數。類中其他方法定義的變數也只是區域變數,而非類的實例屬性。

C#Java中有一類特殊的屬性稱為靜態變數。靜態變數可以被類直接調用,而不被產生實體物件調用。當創建新的產生實體物件後,靜態變數並不會獲得新的記憶體空間,而是使用類創建的記憶體空間。因此,靜態變數能夠被多個產生實體物件共用。在Python中靜態變數稱為類變數,類變數可以在該類的所有實例中被共用。下面這段代碼演示了執行個體變數和類變數的區別。


01      class Fruit:

02          price = 0                                     # 類屬性

03     

04          def __init__(self):

05              self.color = "red"                        # 實例屬性

06              zone = "China"                            # 區域變數

07     

08      if __name__ == "__main__":

09          print(Fruit.price)                            # 使用類名調用類變數

10          apple = Fruit()                               # 產生實體apple

11          print(apple.color)                            # 列印apple實例的顏色

12          Fruit.price = Fruit.price + 10                # 將類變數加10

13          print ("apple's price:" + str(apple.price))   # 列印apple實例的price

14          banana = Fruit()                              # 產生實體banana

15          print ("banana's price:" + str(banana.price)) # 列印banana實例的price


【代碼說明】

·第2行代碼定義了公有的類屬性price,並設置初始值為0

·第5行代碼定義了公有的實例屬性color,並設置值為“red”。實例屬性前需要使用首碼self聲明。

·第6行代碼定義了區域變數zone,該變數不能被Fruit的產生實體物件使用。如果產生實體物件直接引用zonePython將拋出錯誤:AttributeError:Fruit instance has no attribute'zone'

·第9行代碼輸出類屬性price的值。輸出結果為“0”。

·第10行代碼對Fruit類產生實體,生成物件apple

·第11行代碼輸出實例屬性color的值。輸出結果為“red”。

·第12行代碼設置類屬性price的值為10

·第13行代碼輸出apple物件的屬性price的值。輸出結果為“apple's price:10”。

·第14行代碼對Fruit類產生實體,生成物件banana

·第15行代碼輸出banana物件的屬性price的值。輸出結果為“banana's price:10”。

注意  Python的類和物件都可以訪問類屬性,而Java中的靜態變數只能被類調用。

類的外部不能直接訪問私有屬性。如果把color屬性改為私有屬性__color,當執行如下語句時,Python解譯器將不能識別屬性__color


print fruit.__color


Python解譯器將提示如下錯誤。


AttributeError: Fruit instance has no attribute '__color'


Python提供了直接訪問私有屬性的方式,可用於程式的測試和調試。私有屬性訪問的格式如下所示。


instance._classname__attribute


【代碼說明】 instance表示產生實體物件;classname表示類名;attribute表示私有屬性。

Fruit類做一些修改,並採用上述格式調用私有屬性。


01      # 訪問私有屬性

02      class Fruit:

03          def __init__(self):

04              self.__color = "red"            # 私有變數使用雙底線開始的名稱

05             

06      if __name__ == "__main__":

07          apple = Fruit()                     # 產生實體apple

08          print( apple._Fruit__color)         # 調用類的私有變數


【代碼說明】 4行代碼定義了私有屬性__color。第8行代碼輸出私有屬性__color的值。輸出結果為red

上述代碼的主程序可以直接訪問Fruit的屬性color,這種直接暴露資料的做法是不提倡的。因為這種方式可以隨意地更改實例屬性的值,會導致程式資料安全方面的問題。這種訪問方式主要用於開發階段的測試或調試時使用。通常的做法是定義相關的get方法獲取實例屬性的值。例如,在Fruit類中定義getColor()方法。下一節將介紹方法的定義和使用。

注意  Python對類的屬性和方法的定義次序並沒有要求。合理的方式是把類屬性定義在類中最前面,然後再定義私有方法,最後定義公有方法。

類提供了一些內置屬性,用於管理類的內部關係。例如,__dict____bases____doc__等。下面這段代碼演示了常見內置屬性的用法。


01      class Fruit:

02          def __init__(self):

03              self.__color = "red"            # 定義私有變數

04     

05      class Apple(Fruit):                     # Apple繼承了Fruit

06          """This is doc"""                   # doc文檔

07          pass

08             

09      if __name__ == "__main__":

10          fruit = Fruit()

11          apple = Apple()

12          print(Apple.__bases__ )             # 輸出基類組成的元組

13          print(apple.__dict__)               # 輸出屬性組成的字典

14          print(apple.__module__)             # 輸出類所在的模組名

15          print( apple.__doc__)               # 輸出doc文檔


【代碼說明】

·第12行代碼輸出Apple類的父類組成的元組。由於Python支持多重繼承,所以可能存在多個父類。輸出結果:


(<class __main__.Fruit>,)


·第13行代碼輸出apple物件中屬性組成的字典。輸出結果:


{'_Fruit__color': 'red'}


·第14行代碼,當前運行的模組名即為“__main__”。輸出結果:


__main__


·第15行代碼輸出類Appleheredochere文檔)。輸出結果:


"This is doc"


8.3.2 類的方法

類的方法也分為公有方法和私有方法。私有方法不能被模組外的類或方法調用,私有方法也不能被外部的類或函式呼叫。C#Java中的靜態方法使用關鍵字static聲明,而Python使用函數staticmethod()@staticmethod修飾器把普通的函數轉換為靜態方法。Python的靜態方法並沒有和類的實例進行名稱綁定,要調用只需使用類名作為它的首碼即可。下面這段代碼演示了類的方法和靜態方法的使用。


01    class Fruit:

02          price = 0                          # 類變數

03     

04          def __init__(self):

05              self.__color = "red"           # 定義私有變數

06     

07          def getColor(self):

08              print(self.__color)            # 列印私有變數

09         

10          @ staticmethod                     # 使用@staticmethod修飾器靜態方法

11          def getPrice():

12              print(Fruit.price)

13     

14          def __getPrice():                  # 定義私有函數

15              Fruit.price = Fruit.price + 10

16              print(Fruit.price)

17         

18          count = staticmethod(__getPrice)   # 使用staticmethod方法定義靜態方法

19     

20      if __name__ == "__main__":

21          apple = Fruit()                    # 產生實體apple

22          apple.getColor()                   # 使用實例調用靜態方法

23          Fruit.count()                      # 使用類名調用靜態方法

24          banana = Fruit()

25          Fruit.count()

26          Fruit.getPrice()


【代碼說明】

·第7行代碼定義了公有方法getColor(),獲取屬性__color的值。

·第10行代碼使用@staticmethod指令聲明方法getPrice為靜態方法。

·第11行代碼定義getPrice(),該方法沒有self參數。

·第14行代碼定義了方法__getPrice(),該方法並沒有提供參數self

·第18行代碼調用staticmethod()__getPrice()轉換為靜態方法,並賦值給變數countcount()即為Fruit類的靜態方法。

·第22行代碼調用fruit物件的方法getColor(),返回屬性__color的值。

·第23行代碼調用靜態方法count()。由於創建了物件apple,所以靜態屬性price執行一次加10的運算。輸出結果為“10”。

·第25行代碼,由於創建了物件banana,靜態方法count()被第2次調用。靜態屬性price的值為20。輸出結果為“20”。

·第26行代碼,調用靜態方法getPrice()。輸出結果為“20”。

類的外部不能直接訪問私有方法。Python解譯器同樣不能識別方法__getPrice()Python解譯器將提示錯誤:AttributeError:Fruit instance has no attribute'__getPrice'

上述代碼的getColor()方法中有1self參數,該參數是區別方法和函數的標誌。類的方法至少需要1個參數,調用方法時不必給該參數賦值。通常這個特殊的參數被命名為selfself參數表示指向實例物件本身。self參數用於區分函數和類的方法。self參數等價於JavaC#中的this關鍵字,但self必須顯式使用,因為Python是動態語言,沒有提供聲明變數的方式,這樣就無法知道在方法中要賦值的變數是不是區域變數或是否需要保存成實例屬性。當調用Fruit類的getColor()方法時,Python會把函數的調用轉換為grow(fruit)Python自動完成了物件fruit的傳遞任務。

Python中還有一種方法稱為類方法。類方法是將類本身作為操作物件的方法。類方法可以使用函數classmethod()@classmethod修飾器定義。而與實例方法不同的是,把類作為第一個參數(cls)傳遞。把上述程式的靜態方法修改為類方法,修改後的代碼如下:


01      @ classmethod                             # 使用@classmethod修飾器定義類方法

02      def getPrice(cls):

03          print(cls.price)

04     

05      def __getPrice(cls):

06          cls.price = cls.price + 10

07          print(cls.price)

08         

09      count = classmethod(__getPrice)           # 使用classmethod方法定義類方法


可見,類方法的使用和靜態方法是十分相似的。如果某個方法需要被其他實例共用,同時又需要使用當前實例的屬性,則定義為類方法。

注意  self參數的名稱可以是任意合法的變數名。建議使用self作為參數名,便於程式的閱讀和程式的統一。而對於類方法,約定使用cls作為參數名。

8.3.3 內部類的使用

Java可以在類的內部定義類,Python同樣存在這種語法。例如,前面提到的汽車模型。汽車由門、車輪等部件組成,可以設計出汽車、門、車輪等3個類。而門、車輪是汽車的一部分,因此把門、車輪表示的類放到汽車的內部。這種在某個類內部定義的類稱為內部類。內部類中的方法可以使用兩種方法調用。

第一種方法是直接使用外部類調用內部類,生成內部類的實例,再調用內部類的方法。調用格式如下所示。


object_name = outclass_name.inclass_name()

object_name.method()


其中outclass_name表示外部類的名稱,inclass_name()表示內部類的名稱,object_name表示內部類的實例。

第二種方法是先對外部類進行產生實體,然後再產生實體內部類,最後調用內部類的方法。調用格式如下所示。


out_name = outclass_name()

in_name = out_name.inclass_name()

in_name.method()


其中out_name()表示外部類的實例,in_name表示內部類的實例。

下面這段代碼演示了內部類的使用。


01      class Car:

02          class Door:                       # 內部類

03              def open(self):

04                  print("open door")

05          class Wheel:                      # 內部類

06              def run(self):

07                  print("car run")

08     

09      if __name__ == "__main__":    

10          car = Car()                       # 產生實體car

11          backDoor = Car.Door()             # 內部類的產生實體方法一

12          frontDoor = Car.Door()            # 內部類的產生實體方法二

13          backDoor.open()                 

14          frontDoor.open()

15          wheel = Car.Wheel()

16          wheel.run()


【代碼說明】

·第2行代碼定義了內部類Door

·第3行代碼定義了Door類的方法open()

·第5行代碼定義了內部類Wheel

·第6行代碼定義了Wheel類的方法run()

·第10行代碼創建Car類的實例car

·第11行代碼創建Door類的實例backDoor。這裡使用類名前導的方式創建。

·第12行代碼創建Door類的實例frontDoor。這裡使用物件名前導的方式創建。

·第1314行代碼調用open()方法,輸出結果為“open door”。

·第15行代碼創建Wheel類的實例wheel

·第16行代碼調用run(),輸出結果為“car run”。

注意  內部類並不適合描述類之間的組合關係,而應把DoorWheel類的物件作為類的屬性使用。內部類容易造成程式結構的複雜,不提倡使用。

8.3.4 __init__方法

構造函數用於初始化類的內部狀態,為類的屬性設置預設值。C#Java的構造函數是與類同名的方法,而Python的構造函數名為__init____init__方法除了用於定義執行個體變數外,還用於程式的初始化。__init__方法是可選的,如果不提供__init__方法,Python將會給出1個默認的__init__方法。下面這段代碼對Fruit類進行初始化。


01   class Fruit:

02       def __init__(self, color):            # 初始化屬性__color

03              self.__color = color

04              print(self.__color)

05         

06          def getColor(self):

07              print(self.__color)

08         

09      def setColor(self, color):

10              self.__color = color

11              print(self.__colo)r

12     

13      if __name__ == "__main__":

14          color = "red"

15          fruit = Fruit(color)                # 帶參數的構造函數

16          fruit.getColor()

17          fruit.setColor("blue")


【代碼說明】

·第2行代碼,__init__是類Fruit的初始化函數,即構造函數。該函數根據參數color的值初始化屬性__color

·第3行代碼,對Fruit類的屬性__color進行賦值,需要首碼self。否則,Python解譯器將認為__color__init__()中的區域變數。

·第4行代碼輸出賦值後的屬性__color。輸出結果為“red”。

·第9行代碼定義方法setColor(),設置屬性__color的值。如果初始化後需要改變屬性__color的值,則調用setColor()方法。

·第15行代碼生成fruit物件。這裡將調用函數__init__(),並傳遞變數color的值。

·第16行代碼調用方法getColor(),返回屬性__color的值。輸出結果為“red”。

·第17行代碼調用方法setColor(),重新設置屬性__color的值。輸出結果為“blue”。

Python可以隨時調用__init__()函數,而C#Java則不能直接調用構造函數。


fruit.__init__(color)


Java程式師應該非常熟悉上面的代碼,其中獲取資料使用get方法,而設置資料使用set方法。

注意  __init__方法和傳統的開發語言一樣不能返回任何值。

8.3.5 __del__方法

析構函數用於釋放物件佔用的資源。Python提供了析構函數__del__()。析構函數可以顯示釋放物件佔用的資源,是另一種釋放資源的方法。析構函數也是可選的。如果程式中不提供析構函數,Python會在後臺提供預設的析構函數。由於Python中定義了__dels__()的實例將無法被Python迴圈垃圾收集器(gc)收集,所以建議只有在需要時才定義__del__。下面這段代碼演示了Python的函數__del__()的使用方法。


01       class Fruit:

02          def __init__(self, color):          # 初始化屬性__color

03              self.__color = color

04              print(self.__color)

05     

06          def __del__(self):                  # 析構函數

07              self.__color = ""

08              print ("free ...")

09     

10          def grow(self):

11              print("grow ...")

12     

13      if __name__ == "__main__":

14          color = "red"

15          fruit = Fruit(color)                # 帶參數的構造函數

16          fruit.grow()


【代碼說明】

·第6行代碼定義了析構函數__del__()。參數self對於析構函數同樣不可缺少。

·第16行代碼調用方法grow()。當程式執行完方法grow()後,物件fruit將超出其作用域,Python會結束物件fruit的生命週期。輸出結果為“free...”。

如果要顯示的調用析構函數,可以使用del語句。在程式的末尾添加如下語句。


del fruit                           # 執行析構函數


對於C++語言,析構函數是必須的。程式師需要為物件分配記憶體空間,同時也要手動釋放記憶體。使用Python編寫程式可以不考慮後臺的記憶體管理,直接面對程式的邏輯。

8.3.6 垃圾回收機制

Java中並沒有提供析構函數,而是採用垃圾回收機制清理不再使用的物件。Python也採用垃圾回收機制清除物件,Python提供了gc模組釋放不再使用的物件。垃圾回收的機制有許多種演算法,Python採用的是引用計數的方式。當某個物件在其作用域內引用計數為0時,Python就會自動清除該對象。垃圾回收機制很好地避免了記憶體洩漏的發生。函數collect()可以一次性收集所有待處理的物件。下面這段代碼使用gc模組顯式地調用垃圾回收器。


01      import gc

02      

03      class Fruit:

04          def __init__(self, name, color):           # 初始化namecolor屬性

05              self.__name = name

06              self.__color = color

07     

08          def getColor(self):

09              return self.__color                    # 返回color

10     

11          def setColor(self, color):

12              self.__color = color                   # 定義color

13     

14          def getName(self):

15              return self.__name                     # 返回name

16     

17          def setColor(self, name):

18              self.__name = name                     # 定義name

19     

20      class FruitShop:                               # 水果店類

21          def __init__(self):

22              self.fruits = []

23     

24          def addFruit(self, fruit):                 # 添加水果

25              fruit.parent = self                    # Fruit類關聯到FruitShop

26              self.fruits.append(fruit)

27     

28      if __name__ == "__main__":

29          shop = FruitShop()

30          shop.addFruit(Fruit("apple", "red"))

31          shop.addFruit(Fruit("banana", "yellow"))

32          print(gc.get_referrers(shop))              # 列印出shop關聯的所有物件

33          del shop

34          print(gc.collect())                        # 顯式地調用垃圾回收器


【代碼說明】

·第4行代碼,在__init__方法中定義了兩個屬性。__name表示水果的名稱,__color表示水果的顏色。

·第20行代碼定義了FruitShop類,表示水果店。

·第22行代碼為FruitShop類定義了屬性fruitsfruits1個列表,用於存放水果店中的水果。

·第24行代碼定義了方法addFruit(),把物件fruit添加到fruits清單中。

·第25行代碼,設置fruit物件的parent屬性為self。即把FruitShop產生實體對象的引用關聯到添加的fruit對象上。

·第3031行代碼,向shop物件中添加兩個fruit物件。

·第32行代碼,調用函數get_referrers()列出shop物件關聯的所有物件。輸出結果:


[{'_Fruit__name': 'apple', '_Fruit__color': 'red', 'parent': <__main__.FruitShop object at 0x1018da6d0>}, {'_Fruit__name': 'apple', '_Fruit__color': 'yellow', 'parent': <__main__.FruitShop object at 0x1018da6d0>}, {'__doc__': None, '__cached__': None, '__package__': None, 'FruitShop': <class '__main__.FruitShop'>, '__file__': 'gc.py', '__name__': '__main__', '__builtins__': <module 'builtins' (built-in)>, 'shop': <__main__.FruitShop object at 0x1018da6d0>, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x100622f50>,

'Fruit': <class '__main__.Fruit'>, 'gc': <module 'gc' (built-in)>}]


·第33行代碼,刪除shop物件,但是shop物件關聯的其他物件並沒有釋放。

·第34行代碼,調用函數collect()釋放shop物件關聯的其他物件,collect()返回結果表示釋放的物件個數。輸出結果為“7”。

注意  垃圾回收器在後臺執行,對象被釋放的時間是不確定的。如果要設置調用垃圾回收器,可以使用gc模組的函數實現。

8.3.7 類的內置方法

Python類定義了一些專用的方法,這些專用方法豐富了程式設計的功能,用於不同的應用場合。前面提到的__init__()__del__()就是類的內置方法。表8-1列出了類常用的內置方法。

8-1 類常用的內置方法



1.__new__()

__new__()__init__()之前被調用,用於創建實例物件。利用這個方法和類屬性的特性可以實現設計模式中的單例模式。單例模式是指創建唯一物件,單例模式設計的類只能產生實體1個物件。下面這段代碼實現了單例模式。


01    class Singleton(object):

02          __instance = None                        # 定義實例

03     

04          def __init__(self):

05              pass

06     

07          def __new__(cls, *args, **kwd):           # __init__之前調用

08              if Singleton.__instance is None:      # 生成唯一實例

09                  Singleton.__instance = object.__new__(cls, *args, **kwd)

10              return Singleton.__instance


【代碼說明】

·第2行代碼定義了私有的類屬性__instance,初始值為None__instance的值是第1次創建的實例,以後的產生實體操作都將共用這個屬性。因此,就達到了創建唯一實例的目的。

·第7行代碼重新實現了方法__new__()

·第8行代碼判斷當前的實例是否為None,如果為None,則調用父類object__new__()方法,生成1個唯一實例。

·第10行代碼返回唯一實例。

2.__getattr__()__setattr__()__getattribute__()

當讀取物件的某個屬性時,Python會自動調用__getattr__()方法。例如,fruit.color將轉換為fruit.__getattr__(color)。當使用賦值運算式對屬性進行設置時,Python會自動調用__setattr__()方法。__getattribute__()的功能與__getattr__()類似,用於獲取屬性的值。但是__getattribute__()能提供更好的控制,使代碼更健壯。下面這段代碼演示了獲取和設置colorprice屬性的方法。


01      class Fruit(object):

02          def __init__(self, color = "red", price = 0):

03              self.__color = color

04              self.__price = price

05             

06          def __getattribute__(self, name):                   # 獲取屬性的方法

07              return object.__getattribute__(self, name)

08     

09          def __setattr__(self, name, value):                 # 設置屬性的方法

10              self.__dict__[name] = value

11     

12      if __name__ == "__main__":

13             fruit = Fruit("blue", 10)

14             print(fruit.__dict__.get("_Fruit__color"))       # 獲取color屬性的值

15             fruit.__dict__["_Fruit__price"] = 5               使用__dict__進行賦值

16             print(fruit.__dict__.get("_Fruit__price"))       # 獲取price屬性的值


【代碼說明】

·第6行代碼實現了__getattribute__()方法,name表示屬性名。

·第9行代碼實現了__setattr__(),設置屬性的值。name表示屬性名,value表示待設置的值。

·第10行代碼,__dict__字典是類的內置屬性,用於記錄類定義的屬性。字典中的key表示類名,value表示屬性的值。

·第14行代碼獲取color屬性的值,因為color是私有屬性,所以字典的索引表示為“_Fruit__color”。輸出結果為“blue。”

·第15行代碼設置price屬性的值。

·第16行代碼獲取price屬性的值。輸出結果為“5”。

去掉上述代碼中__getattribute__()__setattr__()的實現代碼並不會影響輸出結果。但是這些方法可以實現對屬性的控制,根據屬性名做不同的處理。

注意  Python中並不存在__setattribute__()方法。

3.__getitem__()

如果類中把某個屬性定義為序列,可以使用__getitem__()輸出序列屬性中的各個元素。假設水果店中銷售多種水果,可以通過__getitem__()方法獲取水果店中的每種水果。下面的代碼演示了__getitem__()的使用。


01      class FruitShop:

02           def __getitem__(self, i):        # 獲取水果店的水果

03               return self.fruits[i]      

04     

05      if __name__ == "__main__":

06          shop = FruitShop()

07          shop.fruits = ["apple","banana"]  # fruits賦值

08          print(shop[1])

09          for item in shop:                 # 輸出水果店的水果

10              print(itemend=””)


【代碼說明】

·第2行代碼實現了__getitem__()方法,返回序列fruits中的每個元素。

·第7行代碼給序列屬性fruits賦值,fruits屬性被保存在__dict__字典中。

·第8行代碼輸出結果為“banana”。

·第9行代碼遍歷水果店中的水果,輸出結果為“apple banana”。

4.__str__()

__str__()用於表示物件代表的含義,返回1個字串。實現了__str__()方法後,可以直接使用print語句輸出物件,也可以通過函數str()觸發__str__()的執行。這樣就把物件和字串關聯起來,便於某些程式的實現,可以用這個字串來表示某個類。下面這段代碼把__doc__字串的內容作為物件fruit描述。


01      class Fruit:     

02          '''Fruit'''

03          def __str__(self):                  # 定義物件的字串表示

04              return self.__doc__             # 返回heredoc

05     

06      if __name__ == "__main__":

07          fruit = Fruit()

08          print(str(fruit))                   # 調用__str__

09          print(fruit)                        # 與第8行代碼結果相同


【代碼說明】

·第2行代碼為Fruit類定義了文檔字串。

·第3行代碼實現了__str__(),並返回Fruit類的文檔字串。

·第8行代碼調用內置函數str(),即可觸發__str__()方法。輸出結果為“Fruit類”。

·第9行代碼直接輸出物件fruit,返回__str__()方法的值。輸出結果為“Fruit類”。

注意  __str__()必須使用return語句返回。如果__str__()不返回任何值,執行print語句將出錯。

5.__call__()

在類中實現__call__()方法,可以在物件創建時直接返回__call__()的內容。使用該方法可以類比靜態方法。下面這段代碼演示了__call__()的使用。


01      class Fruit:

02          class Growth:                           # 內部類

03              def __call__(self):

04                  print("grow ...")

05     

06          grow = Growth()                         # 返回__call__的內容

07     

08      if __name__ == '__main__':

09          fruit = Fruit()

10          fruit.grow()                            # 使用實例調用

11          Fruit.grow()                            # 使用類名調用


【代碼說明】

·第2行代碼定義了內部類Growth

·第3行代碼實現了__call__()方法。

·第6行代碼調用Growth(),此時將類Growth作為函數返回。即為外部類Fruit定義方法grow()grow()將執行__call__()內的代碼。

·第10行代碼調用grow(),輸出結果為“grow…”。

·第11行代碼,也可以直接使用Fruit類調用gorw()方法,Fruit類的grow()相當於靜態方法。輸出結果為“grow…”。

8.3.8 方法的動態特性

Python作為動態指令碼語言,編寫的程式具有很強的動態性。可以動態添加類的方法,把某個已經定義的函數添加到類中。添加新方法的語法格式如下所示。


class_name.method_name = function_name


其中class_name表示類名,method_name表示新的方法名,function_name表示1個已經存在的函數,函數的內容即為新的方法的內容。下面這段代碼演示了方法的動態添加。


01      # 動態添加方法

02      class Fruit:  

03          pass  

04       

05      def add(self):                 # 定義在類外的函數

06          print("grow ...") 

07     

08      if __name__ == "__main__":

09          Fruit.grow = add           # 動態添加add函數

10          fruit = Fruit()  

11          fruit.grow()


【代碼說明】

·第2行代碼,定義了Fruit類,Fruit類沒有定義任何方法。

·第5行代碼定義了函數add()

·第9行代碼把函數add()添加到Fruit類中,方法名為grow

·第11行代碼輸出方法grow()的內容。輸出結果為“grow...”。

利用Python的動態特性,還可以對已經定義的方法進行修改。修改方法的語法格式如下所示。


class_name.method_name = function_name


其中class_name表示類名,method_name表示已經存在的方法名,function_name表示1個已經存在的函數,該賦值運算式表示將函數的內容更新到方法。下面這段代碼演示了方法的動態修改。


01      # 動態更新方法

02      class Fruit():     

03          def grow(self):     

04              print("grow ...") 

05     

06      def update():     

07          print ("grow ......")

08     

09      if __name__ == "__main__":

10          fruit = Fruit()     

11          fruit.grow()     

12          fruit.grow = update     # grow方法更新為update()

13          fruit.grow()


【代碼說明】

·第3行代碼,在類Fruit中定義了方法grow()

·第6行代碼定義了函數update()

·第11行代碼調用方法grow(),輸出結果為“grow…”。

·第12行代碼更新方法grow(),把grow()更新為函數update(),方法名不變。

·第13行代碼再次調用方法grow(),輸出結果為“grow……”。

 8.4 繼承

繼承是物件導向的重要特性之一。通過繼承可以創建新類,目的是使用或修改現有類的行為。原始的類稱為父類或超類,新類稱為子類或派生類。繼承可實現代碼的重用。

8.4.1 使用繼承

當類設計完成以後,就可以考慮類之間的邏輯關係。類之間存在繼承、組合、依賴等關係,可以採用UML工具表示類之間的關係。例如,有兩個子類AppleBanana繼承自父類Fruit,父類Fruit中有1個公有的執行個體變數和1個公有的方法。可以用類圖來描述這3個類之間的關係。圖8-2表示了Fruit類和AppleBanana類之間的繼承關係。


8-2 類的繼承關係

公有執行個體變數和方法用+表示。AppleBanana類可以繼承Fruit類的執行個體變數color和方法grow()

繼承可以重用已經存在的資料和行為,減少代碼的重複編寫。Python在類名後使用一對括弧表示繼承的關係,括弧中的類即為父類。如果父類定義了__init__方法,子類必須顯式調用父類的__init__方法。如果子類需要擴展父類的行為,可以添加__init__方法的參數。下面這段代碼演示了繼承的實現。


01     class Fruit:                                    # 基類

02          def __init__(self, color):

03              self.color = color

04              print("fruit's color : %s" % self.color)

05     

06          def grow(self):

07              print("grow ...")

08     

09      class Apple(Fruit):                            # 繼承了Fruit

10          def __init__(self, color):                 # 子類的構造函數

11              Fruit.__init__(self, color)            # 顯式調用父類的構造函數

12              print("apple's color : %s" % self.color)

13     

14      class Banana(Fruit):                           # 繼承Fruit

15          def __init__(self, color):                 # 子類的構造函數

16              Fruit.__init__(self, color)            # 顯式調用父類的構造函數

17              print("banana's color : %s" % self.color)

18     

19          def grow(self):

20              print("banana grow ...")

21     

22      if __name__ == "__main__":

23          apple = Apple("red")

24          apple.grow()                                # 調用grow方法

25          banana = Banana("yellow")

26          banana.grow()                               # 調用grow方法


【代碼說明】

·第1行代碼定義了父類Fruit

·第2行代碼在__init__方法中定義了公有的屬性color

·第6行代碼定義了公有的方法grow()

·第9行代碼定義了子類AppleApple繼承了Fruit

·第11行代碼調用了父類Fruit__init__方法進行初始化。

·第12行代碼使用繼承父類Fruit的屬性color

·第14行代碼定義了子類BananaBanana繼承了Fruit

·第19行代碼定義了和父類Fruit同名的方法grow(),該方法覆蓋了Fruit類的grow()

·第23行代碼創建了物件apple。由於Apple__init__方法調用了Fruit__init__方法,所以先輸出父類中的資訊,再輸出子類中的資訊。輸出結果如下所示。


fruit's color : red

apple's color : red


·第24行代碼調用了apple物件的grow()。由於Apple類繼承了Fruit類,所以直接輸出父類方法grow()中的資訊。輸出結果如下所示。


grow ...


·第25行代碼創建了物件banana。輸出結果如下所示。


fruit's color : yellow

banana's color : yellow


·第26行代碼,由於Banana類的grow()覆蓋了Fruit類的grow(),所以這裡輸出Banana類的方法grow()中的資訊。輸出結果:


banana grow ...


還可以使用super類的super()調用父類的__init__方法。super()的聲明如下所示。


super(type, obj)


其中type是某個類,objtype類的產生實體對象。super()可以綁定type類的父類。下面這段代碼使用super()調用父類的__init__方法。


01      # 使用super()調用父類

02      class Fruit(object):                    # 定義基類,繼承自object,python3.X中這不是必須的

03          def __init__(self):

04              print("parent")

05     

06      class Apple(Fruit):

07          def __init__(self):

08              super(Apple, self).__init__()   # 使用super()調用父類,更直觀

09              print("child")

10     

11      if __name__ == "__main__":   

12          Apple()


【代碼說明】

·第8行代碼在Apple類的__init__()中調用super(),此時的self物件是Apple類的實例。

·第12行代碼創建Apple類的實例。輸出結果如下所示。


parent

child


注意  super類的實現代碼繼承了object,因此Fruit類必須繼承object,如果不繼承object,使用super()將出現錯誤。

8.4.2 抽象基類

使用繼承之後,子類可以重用父類中的屬性和方法,並且可以對繼承的方法進行重寫。例如,AppleBanana都繼承了Fruit類,AppleBanana類都具有父類的grow()方法。Fruit類是對水果的抽象,不同的水果有不同的培養方法,因此生成的情況也是不同的。Fruit類的grow()是所有水果行為的抽象,並不知道如何生長。因此,grow()方法應該是一個空方法,即抽象方法。

抽象基類是對一類事物的特徵行為的抽象,由抽象方法組成。在Python3中可以使用abc模組,這個模組中有一個元類ABCMeta和修飾器@abstractmethod。抽象基類不能被直接產生實體。以下例子演示了怎麼實現一個抽象基類。


01      from abc import ABCMeta, abstractmethod        # 引入所需的module

02      class Fruit(metaclass=ABCMeta):                # 抽象類別

03          @abstractmethod                            # 使用@abstractmethod修飾器定義抽象函數

04          def grow(self):

05              pass

06     

07      class Apple(Fruit):

08          def grow(self):                            # 子類中必須重寫抽象函數

09              print("Apple growing")

10     

11      if __name__ == "__main__":

12          apple = Apple()

13          apple.grow()


【代碼說明】

·第1行代碼從abc模組引入元類ABCMeta和修飾器@abstractmethod

·第2行代碼定義Fruit類,括弧內使用metaclass=ABCMeta定義該類為抽象類別。

·第3~5行代碼使用@stractmethod定義了抽象方法grow(),沒有任何內容。

·第7~9行代碼定義了Apple類,繼承抽象基類Fruit。同時同樣定義了grow()方法。

最終代碼輸出的結果如下:


Apple growing


在上述代碼的最後添加如下代碼,對Fruit類進行產生實體。


fruit = Fruit()


程式將拋出如下異常資訊。


TypeError: Can't instantiate abstract class Fruit with abstract methods grow


Python對物件導向的語法做了簡化,去掉了物件導向中許多複雜的特性。例如,類的屬性和方法的限制符——publicprivateprotectedPython提倡語法的簡單、易用性,這些存取權限靠程式師的自覺遵守,而不強制使用。

8.4.3 多態性

繼承機制說明子類具有父類的公有屬性和方法,而且子類可以擴展自身的功能,添加新的屬性和方法。因此,子類可以替代父類物件,這種特性稱為多態性。由於Python的動態類型,決定了Python的多態性。例如,AppleBanana類繼承了Fruit類,因此AppleBanana具有Fruit類的共性。AppleBanana的實例可以替代Fruit物件,同時又呈現出各自的特性。

FruitShop類中定義sellFruit()方法,該方法提供參數fruitSellFruit()根據不同的水果類型返回不同的結果。下面這段代碼的sellFruit()根據多態性的特性,作出不同的處理。


01      class Fruit:                                 # 父類 Fruit

02          def __init__(self, color = None):

03              self.color = color

04     

05      class Apple(Fruit):                          # 繼承Fruit的子類Apple

06          def __init__(self, color = "red"):

07              Fruit.__init__(self, color)

08     

09      class Banana(Fruit):                         # 繼承Fruit的子類Banana

10          def __init__(self, color = "yellow"):

11              Fruit.__init__(self, color)

12     

13      class FruitShop:

14          def sellFruit(self, fruit):

15              if isinstance(fruit, Apple):         # 判斷參數fruit的類型

16                  print("sell apple")              # 根據類型做特殊的處理

17              if isinstance(fruit, Banana):

18                  print("sell banana")

19              if isinstance(fruit, Fruit):

20                  print("sell fruit")


【代碼說明】

·第14行代碼在FruitShop類中定義了sellFruit(),參數fruit接收AppleBanana類的實例。

·第15~20行代碼分別判斷參數fruit的類型。如果fruit的類型是Apple,則返回“sell apple”;如果fruit的類型是Banana,則返回“sell banana”;如果fruit的類型是Fruit,則返回“sell fruit”。

下面這段代碼分別向sellFruit()傳遞applebanana物件,返回一部分相同的結果和一部分不同的結果。


01     if __name__ == "__main__":

02          shop = FruitShop()

03          apple = Apple("red")

04          banana = Banana("yellow")

05          shop.sellFruit(apple)                 # 參數的多態性,傳遞apple物件

06          shop.sellFruit(banana)                # 參數的多態性,傳遞banana物件


【代碼說明】

·第5行代碼調用sellFruit(),並傳遞了apple物件。輸出結果:


sell apple

sell fruit


·第6行代碼調用sellFruit(),並傳遞了banana物件。輸出結果:


sell banana

sell fruit


Python的多態性的表達方式比Java等強類型語言方便很多。Java實現sellFruit(),需要把其中的參數fruit定義為Fruit類型。而Python不再需要考慮參數的類型了。

8.4.4 多重繼承

Python支援多重繼承,即1個類可以繼承多個父類。多重繼承的語法格式如下所示。


class_name(parent_class1, parent_class2…)


其中class_name是類名,parent_class1parent_class2是父類名。例如,西瓜一般被認為是水果,但是西瓜實質上是一種蔬菜。因此,西瓜既具有水果的特性,又具有蔬菜的特性。水果和蔬菜就可以作為西瓜的父類,西瓜(Watermelon)繼承了水果類(Fruit)和蔬菜類(Vegetable)。圖8-3表示了Watermelon類和FruitVegetable類之間的多重繼承關係。


8-3 類的多重繼承關係

可見Watermelon類同時具有了Fruit類的grow()Vegetable類的plant()。下面這段代碼實現了Watermelon類的多重繼承。


01      class Fruit:

02          def __init__(self):

03              print("initialize Fruit")

04         

05          def grow(self):                          # 定義grow()方法

06              print ("grow ...")

07     

08      class Vegetable(object):

09          def __init__(self):

10              print("initialize Vegetable")   

11     

12          def plant(self):                         # 定義plant方法

13              print("plant ...")

14     

15      class Watermelon(Vegetable, Fruit):          # 多重繼承,同時繼承VegetableFruit

16          pass

17     

18      if __name__ == "__main__":

19          w = Watermelon()                         # 多重繼承的子類,擁有父類的一切公有屬性

20          w.grow()

21          w.plant()


【代碼說明】

·第15行代碼定義了Watermelon類,並同時繼承FruitVegetable類。輸出結果為“initialize Vegetable”。

·第20行代碼調用grow(),輸出結果為“grow…”。

·第21行代碼調用plant(),輸出結果為“plant…”。

作為一般規則,在大多數程式中最好避免多重繼承,但是多重繼承有時有利於定義所謂的混合類(Mixin)。

注意  由於Watermelon繼承了VegetableFruit類,因此Watermelon將繼承__init__()。但是Watermelon只會調用第1個被繼承的類的__init__,即Vegetable類的__init__()

8.4.5 Mixin機制

在討論Mixin機制之前,先看一個水果分類的例子。水果根據去果皮的方法可以分為削皮和剝皮兩類。因此,可以設計削皮水果類(HuskedFruit)和剝皮水果類(Decorticated-Fruit),這兩個繼承自Fruit類。蘋果屬於削皮水果,因此Apple類繼承HuskedFruit類;而香蕉屬於剝皮水果,因此Banana類繼承DecorticatedFruit類。這種設計方式如圖8-4所示。


8-4 水果分類的設計

下面這段代碼實現了如圖8-4所示的繼承關係。


01      class Fruit:                               # 父類

02          def __init__(self):

03              pass

04     

05      class HuskedFruit(Fruit):                  # 削皮水果

06          def __init__(self):

07              print("initialize HuskedFruit")

08         

09          def husk(self):                        # 削皮方法

10              print("husk ...")

11     

12      class DecorticatedFruit(Fruit):            # 剝皮水果

13          def __init__(self):

14              print("initialize DecorticatedFruit")

15     

16          def decorticat(self):                  # 剝皮方法

17              print("decorticat ...")

18     

19      class Apple(HuskedFruit):

20          pass

21     

22      class Banana(DecorticatedFruit):

23          pass


如果按照季節對水果進行分類,整個繼承結構將發生變化。例如,把水果分為夏季水果和冬季水果,這時程式碼將需要作出一些修改。首先要創建新的類型,夏季水果和冬季水果的實現類;然後在繼承結構中添加一層,並把它們分別作為削皮水果和剝皮水果的子類。如果對水果再進行新的分類,整個繼承結構將增加許多的層次,使代碼實現變得十分複雜,而且不易維護。Mixin機制可以改變這個複雜的局面。圖8-5表示了Mixin機制實現的繼承結構。


8-5 Mixin的繼承結構

可見,Mixin機制把Fruit類、HuskdFruit類、DecorticateFruit類放在同1個層次,具體的水果類使用多重繼承的方式繼承所屬的分類。下面這段代碼實現了如圖8-5所示的繼承結構。


01    class Fruit(object):                      # 水果

02          def __init__(self):

03              pass

04     

05      class HuskedFruit(object):                # 削皮水果

06          def __init__(self):

07              print("initialize HuskedFruit")

08         

09          def husk(self):                       # 削皮方法

10              print("husk ...")

11     

12      class DecorticatedFruit(object):          # 剝皮水果

13          def __init__(self):

14              print("initialize DecorticatedFruit")

15     

16          def decorticat(self):                 # 剝皮方法

17              print("decorticat ...")

18     

19      class Apple(HuskedFruit, Fruit):          # 是水果,並且是削皮水果

20          pass

21     

22      class Banana(DecorticatedFruit, Fruit):   # 是水果,並且是剝皮水果

23          pass


Mixin的方法把繼承的體系結構分為3層,父類object層、水果分類層和水果層。Mixin減少了繼承的層次,同時把依賴關係移到了分類層。如果要對水果進行新的分類,只要在第2層中添加新的類型,然後在多重繼承中添加新的類即可。

8.5 運算子的重載

運算子用於運算式的計算,而對於自訂的物件似乎並不能進行計算。運算子的重載可以實現物件之間的運算。Python把運算子和類的內置方法關聯起來,每個運算子都對應1個函數。例如,__add__()表示加號運算子+__gt__()表示大於運算子>。下面這段代碼實現了對運算子+>的重載。


01    class Fruit:

02          def __init__(self, price = 0):

03              self.price = price

04     

05          def __add__(self, other):                  # 重載加號運算子

06              return self.price + other.price

07     

08          def __gt__(self, other):                   # 重載大於運算子      

09              if self.price > other.price:

10                  flag = True

11              else:

12                  flag = False

13              return flag  

14     

15      class Apple(Fruit):

16          pass

17     

18      class Banana(Fruit):

19      pass


【代碼說明】

·第5行代碼對加號運算子進行重載,使Fruit物件的price屬性進行加法運算。

·第8行代碼對大於運算子進行重載,比較price屬性的大小。如果selfprice屬性大於otherprice屬性,則返回True;否則返回False

下面這段代碼對Fruit產生實體物件使用重載後的運算子+>


01   if __name__ == "__main__":

02          apple = Apple(3)

03          print("蘋果的價格:", apple.price)

04          banana = Banana(2)

05          print("香蕉的價格:", banana.price)

06          print(apple > banana)                    # >號為重載後的運算子

07          total = apple + banana                   # +號為重載後的運算子

08          print("合計:", total)


【代碼說明】

·第3行代碼輸出apple物件的price屬性。輸出結果:


蘋果的價格: 3


·第5行代碼輸出banana物件的price屬性。輸出結果:


香蕉的價格:2


·第6行代碼根據price屬性的大小,比較apple物件和banana物件。輸出結果:


True


·第7行代碼對apple物件和banana物件執行加法運算。

·第8行代碼輸出結果:


合計:5


這樣就實現了物件的加法運算和比較運算。物件的加法實際上是對price屬性相加,而物件的比較也是對price屬性進行比較。

C++支持<<>>等流操作符,Python可以對流操作符進行重載,實現類似C++的流操作。下面這段代碼實現了對運算子<<的重載。


01     import sys                        # 引入sys

02     

03      class Stream:

04          def __init__(self, file):

05              self.file=file

06         

07          def __lshift__(self, obj):     # 對運算子<<進行重載

08              self.file.write(str(obj))

09              return self

10     

11      class Fruit(Stream):

12          def __init__(self, price = 0, file = None):

13              Stream.__init__(self, file)

14              self.price = price

15     

16      class Apple(Fruit):

17          pass

18     

19      class Banana(Fruit):

20          pass


【代碼說明】

·第3行代碼定義了Stream類,該類實現了對<<運算子的重載。

·第4行代碼,Stream類的__init__()定義了屬性file,接受流物件。

·第7行代碼對運算子<<進行重載,調用file屬性的write()把物件obj的內容寫入指定設備。

·第11行代碼定義了Fruit類,並繼承Stream類,使Fruit類的物件能夠使用運算子<<

下面這段代碼創建了applebanana物件,並使用運算子<<輸出price屬性的值。


01      if __name__ == "__main__":

02          apple = Apple(2, sys.stdout)         # apple對象可作為流輸出

03          banana = Banana(3, sys.stdout)

04          endl = "\n"      

05          apple<<apple.price<<endl             # 使用重載後的<<運算子

06          banana<<banana.price<<endl


【代碼說明】

·第2行代碼把apple對象作為流輸出,輸出結果到控制台中。

·第3行代碼把banana對象作為流輸出,輸出結果到控制台中。

·第4行代碼定義結束符。

·第5行代碼輸出結果為“2”。

·第6行代碼輸出結果為“3”。

 8.6 Python與設計模式

設計模式(design pattern)是物件導向程式設計的解決方案,是複用性程式設計的經驗總結。本節講解Python與設計模式。

8.6.1 設計模式簡介

設計模式的概念最早起源于建築設計大師Christopher Alexander關於城市規劃和建築設計的著作《建築的永恆方法》,儘管該著作是針對建築領域的,但是其思想也適用於軟體發展領域。設計模式是對實際專案中軟體設計的不斷總結,經過了一次次修改、重構而形成的解決方案。因此合理使用設計模式可以設計出優秀的軟體。設計模式的目標是形成典型問題的解決方案,設計出可複用的軟體結構。設計模式是與語言無關的,任何語言都可以實現設計模式。

設計模式根據使用目的不同而分為創建型模式(creational pattern)、結構型模式(structural pattern)和行為型模式(behavioral patterns)。

創建型模式提出了物件創建的解決方案,以及資料封裝的方法。降低了創建物件時代碼實現的複雜度,使物件的創建能滿足特定的要求。例如,工廠模式、抽象工廠模式、單例模式、生成器模式等。

結構型模式描述了物件之間的體系結構,通過組合、繼承等方式改善體系結構,降低體系結構中元件的依賴性。例如,適配器模式、橋模式、組合模式、裝飾器模式、面板模式等。

行為型模式描述了物件之間的交互和各自的職責,有助於實現程式中物件的通信和流程的控制。例如,反覆運算器模式、解譯器模式、仲介者模式、觀察者模式等。

Python是一種簡單、靈活的動態語言,Python簡化了物件導向語法的複雜性,但卻沒有降低物件導向的特性。使用Python同樣可以實現各種設計模式,而且實現過程比較簡單。

8.6.2 設計模式示例——Python實現工廠方法

在工廠方法模式中,工廠方法用於創建產品,並隱藏了產品物件產生實體的過程。工廠方法根據不同的參數生成不同的物件。因此,客戶程式只需要知道工廠類和產品的父類,並不需要知道產品的創建過程,以及返回的產品類型。例如,定義Factory類創建不同的Fruit物件。Apple類和Banana類繼承自Fruit類,AppleBanana可以看作是具體的產品。根據輸入的字串命令,返回Fruit物件。如圖8-6所示描述了工廠方法的類圖。


8-6 工廠方法的類圖

下面這段代碼實現了工廠方法。


01      class Factory:                            # 工廠類

02          def createFruit(self, fruit):         # 工廠方法

03              if fruit == "apple":              # 如果是apple則返回類Apple

04                  return Apple()          

05              elif fruit == "banana":           # 如果是banana則返回類Banana

06                  return Banana()

07     

08      class Fruit:

09          def __str__(self):

10              return "fruit"

11     

12      class Apple(Fruit):

13          def __str__(self):

14              return "apple"

15     

16      class Banana(Fruit):

17          def __str__(self):

18              return "banana"

19     

20      if __name__ == "__main__":

21          factory = Factory()

22          print(factory.createFruit("apple")  )  # 創建apple對象

23          print(factory.createFruit("banana"))   # 創建banana對象


【代碼說明】

·第1行代碼定義了工廠類FactoryFactory類生成Fruit物件。

·第2行代碼定義工廠方法createFruit(),根據字串類型fruit的值生成不同的Fruit物件。如果fruit的值為“apple”,則調用Apple(),返回Apple類的實例;如果fruit的值為“banana”,則調用Banana(),返回Banana類的實例。

·第21行代碼創建了Factory類的實例factory

·第22行代碼輸出結果為“apple”。

·第23行代碼輸出結果為“banana”。

工廠方法包含了前面講解的物件導向的方法,使用了繼承、封裝等特性。工廠方法的缺點是在添加新產品時,需要編寫新的具體產品類,而且還要修改工廠方法的實現。工廠方法模式的應用非常廣泛,通常用於設計物件的生成以及系統的框架。

 8.7 小結

本章介紹了Python物件導向的程式設計。首先介紹了物件導向的基本概念以及UML建模語言。講解了Python中類的定義、屬性和方法的創建以及類的內置方法等知識,比較了PythonJava等語言物件導向的實現區別。重點講解了Python物件導向中的動態特性、多重繼承和Mixin機制。Python再次引入了多重繼承機制,而且比C++更容易實現和運用。Mixin機制優化了多重繼承的設計,是一種新的設計方法。Python也支持運算子的重載,並實現了加號運算子和流運算子的重載。最後介紹了Python對設計模式的支援,實現了常見的工廠類。

下一章將介紹Python的異常處理和程式調試技術。異常和錯誤的處理一直是程式設計語言面對的一個課題。Python作為一門健壯的指令碼語言,提供了豐富的異常類和錯誤類。同時,將使用Pycharm工具和原生的Python IDE講解程式的調試,以及常見問題的處理。

 8.8 習題

1.怎麼查看一個類的所有屬性?

2.為什麼創建類函數的時候需要添加self變數?可以使用其他變數如this代替self嗎?

3.怎麼理解Python中一切皆物件?

4.汽車、輪船、火車等都屬於交通工具。聯繫實際情況,編寫類Traffic,並繼承它實現類CarShipTrain

 


0 留言:

發佈留言