- Python高效開發實戰:Django、Tornado、Flask、Twisted(第3版)
- 劉長龍
- 3685字
- 2021-10-15 17:52:48
1.6 面向對象編程
Python從被設計之初就是一門面向對象的語言,正因為如此,在Python中創建一個類和對象是很容易的。本節從面向對象的概念說起,帶領讀者掌握用Python網絡框架進行開發所必需的面向對象編程知識。
1.6.1 什么是面向對象
面向對象程序設計(Object Oriented Programming,OOP)是一種程序設計范型,也是一種程序開發方法。對象指的是類的實例,類是創建對象的模板,一個類可以創建多個對象,每個對象都是類類型的一個變量;創建對象的過程也叫作類的實例化。面向對象程序設計將對象作為程序的基本單元,將程序和數據封裝其中,以提高軟件的重用性、靈活性和擴展性。面向對象編程中的主要概念如下。
? 類(class):定義了一件事物的抽象特點。通常來說,類定義了事物的屬性和它可以做到的行為。舉例來說,設計一個電子畫板程序中的“Figure”類,它包含二維圖形的一切基本特征,即所有二維圖形共有的特征或行為,例如它的制作者、顏色、是否實心等。類可以為程序提供模板和結構。一個類中可以有成員函數和成員變量。在面向對象的術語中,成員函數被稱為方法,成員變量被稱為屬性。
? 對象(object):是類的實例。例如,“Figure”類定義了圖形的概念,而在電子畫板程序中畫出一個圖形時,則建立了一個Figure類的實例,即對象。當一個類被實例化時,它的屬性就有了具體的值,如該圖形有了作者、某種具體的顏色。每個類都可以有若干被實例化的對象。在操作系統中,系統給對象分配內存空間,而不會給類分配內存空間。
? 繼承(inheritance):是指通過一個已有的類(父類)定義另外一個類(子類),子類共享父類開放的屬性和方法。子類的對象不僅是子類的一個實例,而且是其父類的一個實例。比如,可以從圖形父類Figure繼承并定義一個方形子類Rectangle,它具備Figure類的一切特征,并具備自己的獨有特征,比如長度、寬度。在畫板上畫出一個方形實例時,它就是一個Rectangle,也是一個Figure。
? 封裝性(Encapsulation):是指類在定義時可以將不能或不需要其他類知道的成員定義成私有成員,而只公開其他類需要使用的成員,以達到信息隱蔽和簡化的作用。在電子畫板程序的Figure類中,可以定義Move方法為公開成員,而Move方法需要調用的其他成員(clear、paintline、paint_color等)可以被定義為私有成員。
? 多態性(Polymorphism):是指同一方法作用于不同的對象,可以有不同的解釋,產生不同的執行結果。在具體實現方式上,多態性是允許開發者將父對象的變量設置為對子對象的引用,賦值之后,父對象變量就可以根據當前的賦值給它的子對象的特性以不同的方式運作。比如,設計Figure的兩個子類:圓形Circle和方形Rectangle,兩個子類的繪制(Paint)實現方法肯定不相同,因此當用父對象變量分別引用并調用兩個子對象的Paint方法時會產生不同的效果。
隨著面向對象編程的普及,面向對象設計(Object Oriented Design,OOD)也日臻成熟,形成了以UML(Unified Modeling Language)為代表的標準建模語言。UML是一個支持模型化和軟件系統開發的圖形化語言,為軟件開發的所有階段提供模型化和可視化支持,包括由需求分析到規格,再到構造和配置的所有階段。
1.6.2 類和對象
類和對象是面向對象編程的基礎,本節我們學習類的基本定義、對象的使用方法等。
1. 基本使用
在Python中通過關鍵字class實現類的定義,其語法為:

在塊block_class中寫入類的成員變量及函數。如下是一個類MyClass的定義:

類定義代碼的解析如下。
? 類名為MyClass。
? 類中定義了一個成員變量message,并對其賦了初始值。
? 類中定義了成員函數show(self),注意類中的成員函數必須要帶參數self。
? 參數self是對象本身的引用,在成員函數體中可以引用參數self獲得對象的信息。
使用該類的代碼如下:

通過在類名后面加小括號可以直接實例化類來獲得對象變量,使用對象變量可以訪問類的成員函數及成員變量。
注意:Python中直接在類作用域中定義的成員變量相當于C、C++中的靜態成員變量,既可以通過類名訪問,也可以通過對象訪問。因此,類和所有該類的對象共享同一個成員變量。
2. 構造函數
構造函數是一種特殊的類成員方法,主要用來在創建對象時初始化對象,即為對象成員變量賦初始值。Python中的類構造函數用__init__命名,為MyClass添加構造函數方法,并實例化一個對象。
【示例1-34】構造函數的示例代碼如下:

構造函數在MyClass被實例化時被Python解釋器自動調用,輸出代碼如下:

【示例1-35】如果需要用多種方式構造對象,則可通過默認參數的方式實現:


在上述代碼中定義了3個構造函數,分別接收0、1、2個構造參數,之后分別通過不同的構造參數構造實例。代碼的運行結果如下:

注意:在構造函數中不能有返回值。
如果開發者試圖調用未被定義過的構造函數,比如:

將會導致異常:

技巧:Python中不能定義多個構造函數,但可以通過為命名參數提供默認值的方式達到用多種方式構造對象的目的。
3. 析構函數
析構函數是構造函數的反向函數,在銷毀(釋放)對象時將調用它們。析構函數往往用來做“清理善后”的工作,如數據庫鏈接對象可以在析構函數中釋放對數據庫資源的占用。Python中為類定義析構函數的方法是在類中定義一個名為__del__的沒有返回值和參數的函數。
與Java類似,Python解釋器的堆中儲存著正在運行的應用程序所建立的所有對象,但是它們不需要程序代碼來顯式地釋放,因為Python解釋器會自動跟蹤它們的引用計數,并自動銷毀(同時調用析構函數)已經不再被任何變量引用的對象。在這種場景中,開發者并不知道對象的析構函數何時會被調用。同時,Python提供了顯式銷毀對象的方法:使用del關鍵字。
【示例1-36】為MyClass類添加析構函數的代碼如下:

用del釋放對象時析構函數會自動被調用,代碼運行結果如下:

4. 實例成員變量
在之前的例子中,MyClass類中的成員變量message是類成員變量,即MyClass類和所有MyClass對象共享該成員變量。那么如何定義屬于每個對象自己的成員變量呢?答案是在構造函數中定義self引用中的變量,這樣的成員變量在Python中叫作實例成員變量。
【示例1-37】實例成員變量的示例如下:


本例在構造函數__init__中定義了兩個實例成員變量:self.name和self.color。在MyClass的成員函數(如本例中的show()函數和析構函數)中可以直接使用這兩個成員變量,通過實例名也可以訪問實例成員變量(如本例中的inst2.color、inst3.name)。代碼的運行結果如下:

5. 靜態函數和類函數
到目前為止,讀者在本書中接觸到的類成員函數均與實例綁定,即只能通過對象訪問而不能通過類名訪問。Python中支持兩種基于類名訪問成員的函數:靜態函數和類函數,它們的不同點是類函數有一個隱性參數cls用來獲取類信息,而靜態函數沒有該參數。靜態函數使用裝飾器@staticmethod定義,類函數使用裝飾器@classmethod定義。
【示例1-38】靜態函數和類函數的代碼示例如下:


該段代碼中定義了靜態函數printMessage(),在其中可以訪問類成員變量MyClass.message,可以通過類名對它進行調用;代碼中還定義了類方法createObj(),類方法定義中的第1個參數必須為隱性參數cls,在類方法createObj()中可以通過隱性參數cls替代類名本身,本例中的createObj建立并返回了一個MyClass實例。代碼的運行結果如下:

6. 私有成員
封裝性是面向對象編程的重要特點,Python也提供了將不希望外部看到的成員隱藏起來的私有成員機制。但不像大多數編程語言用Public、Private關鍵字表達可見范圍,Python使用指定變量名格式的方法定義私有成員,即所有以雙下畫線“__”開始命名的成員都為私有成員。
【示例1-39】私有成員代碼示例如下:

本例中的構造函數將實例成員參數設置為私有形式,不影響在類本身的其他成員函數中訪問這些變量(本例在析構函數中訪問了__name屬性)。但是類之外的代碼無法訪問私有成員,比如,如下代碼在運行中將產生AttributeError異常:

1.6.3 繼承
類之間的繼承是面向對象設計的重要方法,通過繼承可以達到簡化代碼和優化設計模式的目的。Python在定義類時可以在小括號中指定基類,所有Python類都是object類型的子類,語法如下:

【示例1-40】子類除了具備自己block_class中定義的特性,還從父類中繼承了父類的非私有特性,舉例如下:

解析如下。
? 定義了一個基類Base,基類繼承自object,并且定義了構造函數、析構函數、成員函數move()。
? 定義了子類SubA,繼承自Base類,定義、重載了自己的構造函數、成員函數move()。
? 定義了子類SubB,繼承自Base類,定義、重載了自己的析構函數。析構函數中用super關鍵字調用基類的析構函數__del__()。
? 完成類的定義后,分別實例化了兩個子類的對象,并調用了它們的move()方法和析構函數。
技巧:在子類成員函數中,用super關鍵字可以訪問父類成員,其引用方法為super (Sub ClassName, self)。
代碼的執行結果如下:

對結果的解析如下。
? instA調用了子類SubA自己的構造函數和move()方法,但因為SubA沒有重載析構函數,所以對象銷毀時系統調用了基類Base的析構函數。
? 子類SubB只重載了析構函數,所以instB調用了基類的構造函數和move()方法,在對象銷毀時調用了SubB自己的析構函數。
? move()方法在被instA和instB調用時分別展現了不同的行為,這種現象是多態。
技巧:在子類的析構函數中調用基類的析構函數是一種最佳實踐,不這樣做可能導致父類資源不能如期被釋放。
【示例1-41】Python中允許類的多繼承,也就是一個子類可以有多個基類,舉例如下:


該段代碼中定義了兩個基類,兩個基類中都定義了move()方法。BaseC繼承自BaseA并且重載了move()函數。Sub繼承自BaseC和BaseB,并且沒有定義自己的成員。調用子類對象的move()方法的結果如下:

此處讀者需要體會的是:當子類繼承了多個父類,并且調用一個在幾個父類中共有的成員函數時,Python解釋器會選擇距離子類最近的一個基類的成員方法。本例中Sub繼承自BaseC和BaseB,所以move()方法的搜索順序是Sub→BaseC→BaseA→BaseB。
注意:設計多父類的繼承關系時,要盡量避免多個父類中出現同名成員。如果不可避免,則應當留意子類定義中引用父類的順序。
- 深度實踐OpenStack:基于Python的OpenStack組件開發
- 深入淺出WPF
- 數據結構習題精解(C語言實現+微課視頻)
- Animate CC二維動畫設計與制作(微課版)
- Cassandra Data Modeling and Analysis
- SSM輕量級框架應用實戰
- 利用Python進行數據分析(原書第3版)
- Linux:Embedded Development
- iOS開發實戰:從入門到上架App Store(第2版) (移動開發叢書)
- 區塊鏈技術與應用
- Python網絡爬蟲技術與應用
- C# 7.0本質論
- Getting Started with RethinkDB
- Getting Started with the Lazarus IDE
- Mathematica Data Visualization