day6-面向對象基礎篇


一、面向對象引子及概念

結合編程的一些理論知識和實踐,可以總結出目前存在以下編程模式:

1. 面向過程

按照業務邏輯和實現過程步驟來逐步壘代碼,代碼編寫的邏輯即對應於實際實現的步驟過程,核心是過程兩個字,從代碼執行順序上體現出設計者的邏輯過程,整個程序就是把若干個過程串起來的效果。本質上像是構建了一條生成流水線,每一道工序都通過代碼塊嚴格定義。

優點

復雜問題簡單化,把大的任務逐步分解成一個一個小的任務分步實現,實現了每個小的步驟即可完成整體任務。邏輯思想符合日常生活中的常規過程化思維,因而代碼可讀性較高。

缺點

由於實現邏輯是自上而下的分步過程,任何一個環節的變動,都可能涉及到后續環節的變動,牽一發而動全身。因此代碼的靈活性、可維護性較差:構建的生產面包的流水線,如果原料工藝發生變化,很可能要進行生產線的改造;當然想用它來生產飲料,幾乎要推倒重來。

應用場景:

需求相對固化,不會經常變更,容易通過過程化思維順序體現出來的情況。

2. 函數式編程

函數式編程也是一種面向過程的編程范式,主要變化在於把重要的功能邏輯通過函數進行封裝,以便重復調用,同時也提高了代碼的維護性。其優點就不贅述了,缺點除了牽一發而動全身外(函數的定義發生變化,至少調用它的每個地方都要隨之改變),對於較為復雜的應用場景,遇到多個函數需要傳遞共同的參數時,你就會覺得寫起來真是麻煩又啰嗦。如果要實現一個數據庫連接查詢修改的程序,數據庫的連接參數是共性參數,如果根據數據庫增刪改查的不同分別定義函數,數據庫連接參數就要重復多次,而如果通過一個函數來定義並區分,你還得各種if去判斷要執行的sql到底屬於哪一種。

總之函數式編程不是萬能的,有時候雖然能實現結果,但實現的過程可能比較曲折。

3. 面向對象

核心是對象二字,(要理解對象為何物,必須把自己當成上帝,上帝眼里世間存在的萬物皆為對象,不存在的也可以創造出來。面向對象的程序設計好比如來設計西游記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特征和技能(這就是對象的概念,特征和技能分別對應對象的數據屬性和方法屬性),然而這並不好玩,於是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是對象。然后取經開始,師徒四人與妖魔鬼怪神仙交互着直到最后取得真經。如來根本不會管師徒四人按照什么流程去取),對象是特征與技能的結合體,基於面向對象設計程序就好比在創造一個世界,你就是這個世界的上帝,存在的皆為對象,不存在的也可以創造出來,與面向過程機械式的思維方式形成鮮明對比,面向對象更加注重對現實世界的模擬,是一種“上帝式”的思維方式。

這部分文字摘抄自http://www.cnblogs.com/linhaifeng/articles/6182264.html,是目前接觸到的解釋接比較接地氣的說法。個人理解,面向對象是一個抽象歸類的思維過程,先梳理出具有共同屬性的個體對象,然后對他們抽象歸類。這樣整個編程的設計思想不再是面向過程中的自上而下的串聯順序關系,而變成一種屬於和不屬於,具有或者不具有(某種屬性)的關系。

優點:

對於復雜的場景,功能實現的靈活性更好,換言之適配性和擴展性更好。

缺點:

面向對象是一個抽象的過程,不同於面向對象的流水線式順序執行過程,編程的復雜程度更高,首要前提是要梳理出對象之間的關系,歸類出相應的屬性,對於開發者而言要求更高,需具備一定的儲備之后才能勝任一般場景的編程開發。

應用場景:

需求經常變化且相對復雜的軟件,一般需求的變化都集中在用戶層,互聯網應用,游戲等都是面向對象的程序設計大顯身手的好地方。

二、類和對象

2.1 類和對象的基本概念

梳理一下類和對象的概念:

類更像是一個模板,是對眾多具備相似特征(屬性)和技能(方法/函數)的統一集中實現,略顯抽象(不做實例化似乎感受不到它的存在)

對象是根據類這個模板創建的實例,是對具備某種特征(屬性)和技能(方法/函數)的具體化個體式的實現(實例化的對象作為一個獨立的個體存在)。有了對象這一具體的實例,才能讓類中描述的特征(屬性)和技能(方法/函數)能真正落地體現出來,否則類只是構想中的空中樓閣。

2.2 類和對象的關系

在面向對象的編程范式下,我們需要通過類和對象來實現編程設計,因此面向對象是對類和對象的應用。

以下文字轉自海峰老師博客http://www.cnblogs.com/linhaifeng/articles/6182264.html

類即類別、種類,是面向對象設計最重要的概念,對象是特征與技能的結合體,而類則是一系列對象相似的特征與技能的結合體

那么問題來了,先有的一個個具體存在的對象(比如一個具體存在的人),還是先有的人類這個概念,這個問題需要分兩種情況去看

  • 在現實世界中:先有對象,再有類

世界上肯定是先出現各種各樣的實際存在的物體,然后隨着人類文明的發展,人類站在不同的角度總結出了不同的種類,如人類、動物類、植物類等概念

也就說,對象是具體的存在,而類僅僅只是一個概念,並不真實存在

  • 在程序中:務必保證先定義類,后產生對象

這與函數的使用是類似的,先定義函數,后調用函數,類也是一樣的,在程序中需要先定義類,后調用類

不一樣的是,調用函數會執行函數體代碼返回的是函數體執行的結果,而調用類會產生對象,返回的是對象

轉述一段形象解釋類和對象的關系的文字,出處http://blog.csdn.net/Four_Infinite/article/details/52795877

• “類提供默認行為,是實例的工廠”,我覺得這句原話非常經典,一下道破了類和實例的關系。看上面代碼,體會一下,是不是這個理?所謂工廠,就是可以用同一個模子做出很多具體的產品。類就是那個模子,實例就是具體的產品。所以,實例是程序處理的實際對象。
• 類是由一些語句組成,但是實例,是通過調用類生成,每次調用一個類,就得到這個類的新的實例

2.3 創建類和對象

圖片轉自https://www.cnblogs.com/wupeiqi/p/4493506.html

  • class是關鍵字,表示類
  • 創建對象,類名稱后加括號即可

ps: 類中的函數第一個參數必須是self(下文會詳述類的三大特性)
   類中定義的函數叫做 “方法”,但__init__有特殊的叫法,稱為構造函數,或構造方法,或初始化方法,用於定義類的原始屬性,在實例化類時自動調用
       類名首字母大寫,這是約定俗成的編程習慣,遵照執行即可

2.4 構造函數

上文提到了構造函數__init__(),又稱之為苟晗函數或構造方法,這里有必要展開闡述一下。

構造函數是一種特殊類型的方法(函數),它在類的實例化對象時被調用。 構造函數通常用於初始化(賦值)給實例變量。它在生成對象時被調用,在創建一個類時,有一些在實例化時必須綁定某些屬性時,可以定義一個__init__方法將其強制填寫進去。
先來簡單的栗子:

  1 # !/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 __author__ = 'Maxwell'
  4 
  5 class Person:
  6     def __init__(self, name, age, gender):
  7         self.name = name
  8         self.age = age
  9         self.gender = gender
 10 
 11 person1 = Person('ZhangSan', '18', 'man')
 12 print('name:%s\t age:%s\t gender:%s' %(person1.name, person1.age, person1.gender))
 13 
 14 運行結果:
 15 name:ZhangSan	 age:18	 gender:man
 16 
View Code

 

還是引用一段解釋比較到位的文字來闡述吧(原文出處http://blog.csdn.net/Four_Infinite/article/details/52795877

我們需要先明確一個基本概念,類就是一種對象類型,和跟前面學習過的數值、字符串、列表等等類型一樣。比如這里構建的類名字叫做Person,那么就是我們要試圖建立一種對象類型,這種類型被稱之為Person,就如同有一種對象類型是list一樣。
在構建Person類的時候,首先要做的就是對這種類型進行初始化,也就是要說明這種類型的基本結構,一旦這個類型的對象被調用了,第一件事情就是要套用這個類型的基本結構,也就是類Person的基本結構。就好比我們每個人,在頭腦中都有關於“人”這樣一個對象類型(對應着類),一旦遇到張三(張三是一個具體人),我們首先套用“人”這個類的基本結構:一個鼻子兩只眼,鼻子下面一張嘴。如果張三符合這個基本機構,我們不會感到驚詫(不報錯),如果張三不符合這個基本結構(比如三只眼睛),我們就會感到驚詫(報錯了)。

由於類是我們自己構造的,那么基本結構也是我們自己手動構造的。在類中,基本結構是寫在__init__()這個函數里面。故這個函數稱為構造函數,擔負着對類進行初始化的任務。

還是回到Person這個類,如果按照上面的代碼,寫好了,是不是__init()__就運行起來了呢?不是!這時候還沒有看到張三呢,必須看到張三才能運行。所謂看到張三,看到張三這樣一個具體的實實在在的人,此動作,在python中有一個術語,叫做實例化。當類Person實例化后立刻運行__init()__函數。

注意:

  • 構造函數用於實例化后對對象進行初始化,但類中構造函數不是必須要顯式定義的,需要的時候才定義,我們可以把類中需要自動綁定的一些屬性通過構造函數來初始化實現,但也並非必須這么做。
  • 當創建沒有構造函數的類時,Python會自動創建一個不執行任何操作的默認構造函數
  • 每個類必須有一個構造函數,即使它只依賴於默認構造函數。
  • 只要實例化一個類(創建對象),構造函數就被會自動調用,不管它是人為顯式定義的,還是調用的默認的空的構造函數

當然,大多數情況下,我們還是會通過顯式定義構造函數,來為類定義一些必要的屬性。

2.5 self關鍵字

細心的朋友會發現在構造函數中會出現一個self關鍵字,定義某一屬性時會通過self.屬性 = 屬性來實現,並且這樣定義后,創建出一個對象時傳入的屬性參數就真的綁定到對象上去了,為什么可以這么神奇呢?

python類中的self關鍵字相當於java中的this關鍵字,它代表類的實例對象本身,創建一個什么對象self就代表那個對象。這個關鍵字在構造函數中不能省略,在類中的其他方法中如果有需要調用到對象,也不能省略,但是在創建對象時,以及之后通過對象調用類中的方法時,self參數會自動傳入,無需手動填寫。有一個說法是self關鍵字也是約定俗成這么寫了,如果非要用其他關鍵字來代替,理論上也可以,不過太另類了。

以下實例和文字分析轉自http://www.cnblogs.com/xiangjun555/p/6992374.html

  1 class dog(object):   #用class定義類
  2     "dog class"     #對類的說明
  3 
  4     def __init__(self,name):   #構造函數或者是構造方法,也可以叫初始化方法
  5         self.name = name
  6 
  7 
  8     def sayhi(self):    #類方法
  9         "sayhi funcation"    #對類方法的說明
 10 
 11         print("hello,i am a dog,my name is ",self.name)
 12 
 13 
 14 d = dog("alex")   #定義一個d的對象,叫實例
 15 d.sayhi()    #調用實例的方法
View Code

self的傳遞過程如下:

其實self 這個關鍵字相當於實例化對象本身(self相當於d),在實例化過程中,把自己傳進去了,我們對上面的兩行做一下解釋:

2.6 類的實例化過程分析

有了以上章節的基礎,下面我們可以來總結一下類的實例化過程了,先上圖把(轉自http://www.cnblogs.com/xiangjun555/p/6992374.html

 

用文字概述起來就是:
1. 通過實例化語句調用類(就像調用函數一樣),在內存中開辟一塊空間,為其命名
2. 把對象的地址和參數傳入構造函數,實現必要屬性的綁定
3. 返回創建的對象(將各個屬性保存到對象中)

2.7 對象和類在內存中如何保存

以下內容轉自https://www.cnblogs.com/wupeiqi/p/4493506.html

類和類中定義的方法,在內存中只保存一份, 而基於類實例化創建的每個對象,都需要在內存中存一份,如下圖所示:

如上圖所示,根據類創建對象時,對象中除了封裝 name 和 age 的值之外,還會保存一個類對象指針,該值指向當前對象的類。

當通過 obj1 執行 【方法一】 時,過程如下:

  1. 根據當前對象中的類對象指針 找到類中的方法(方法保存在類中)
  2. 將對象 obj1 當作參數傳給 方法的第一個參數 self

2.8 析構函數__del__()

析構函數__del__()有點兒跟構造函數反着干的意思,用於對變量、對象的刪除處理,刪除變量、對象到內存的引用關系,進而觸發python的垃圾回收機制回收內存(定期檢測沒有被引用的內存地址,然后清除)。當使用del 刪除對象時,會調用他本身的析構函數,另外當對象在某個作用域中調用完畢,在跳出其作用域的同時析構函數也會被調用一次,這樣可以用來釋放內存空間。__del__()也是可選的,如果不提供,則Python 會在后台提供默認析構函數

三、面向對象三大特性

面向對象的三大特性是指:封裝、繼承和多態。

3.1 封裝

從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八,還有alex一起裝進麻袋,然后把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當片面的。這段論述轉自http://www.cnblogs.com/linhaifeng/articles/7340801.html
最好的例證是即便我們在類中設置了私有屬性,我們還是可以通過類名和屬性名稱拼接出來去訪問它(具體例子就不贅述了,網上很多),因此隱藏並非封裝的目的。
還是繼續引用大神的博客(沒辦法,人寫的好啊),出處http://www.cnblogs.com/linhaifeng/articles/7340801.html

封裝的真諦在於明確地區分內外,封裝的屬性可以直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,需要我們為其開辟接口,讓外部能夠間接地用到我們隱藏起來的屬性,那這么做的意義何在???封裝后調用者無需關心它的內部邏輯細節,只需要按照規定的方式調用即可。
封裝分為封裝屬性和封裝方法,我們分開闡述把:
1. 封裝數據屬性
將數據隱藏起來這不是目的。隱藏起來然后對外提供操作該數據的接口,然后我們可以在接口附加上對該數據操作的限制(有些地方會強調防止數據被隨意修改),以此完成對數據屬性操作的嚴格控制。

  1 class Teacher:
  2     def __init__(self,name,age):
  3         # self.__name=name
  4         # self.__age=age
  5         self.set_info(name,age)
  6 
  7     def tell_info(self):
  8         print('姓名:%s,年齡:%s' %(self.__name,self.__age))
  9     def set_info(self,name,age):
 10         if not isinstance(name,str):
 11             raise TypeError('姓名必須是字符串類型')
 12         if not isinstance(age,int):
 13             raise TypeError('年齡必須是整型')
 14         self.__name=name
 15         self.__age=age
 16 
 17 
 18 t=Teacher('egon',18)
 19 t.tell_info()
 20 
 21 t.set_info('egon',19)
 22 t.tell_info()
View Code

2. 封裝方法
目的是隔離復雜度
封裝方法舉例:
(1). 你的身體沒有一處不體現着封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
(2). 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝並不是單純意義的隱藏!!!
(3). 快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了
提示:在編程語言里,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。
上一個實際例子:
在日常生活中,取款是一個功能,而這個功能有很多功能組成:插卡、密碼驗證、輸入金額、打印賬單、取款,但是對於使用者來說,只需要知道取款這個功能就可以了,其余的功能我們都隱藏起來,很明顯這樣做就隔離了復雜度,同時也提升了安全性

  1 class ATM:
  2     def __card(self):
  3         print('插卡')
  4     def __auth(self):
  5         print('用戶認證')
  6     def __input(self):
  7         print('輸入取款金額')
  8     def __print_bill(self):
  9         print('打印賬單')
 10     def __take_money(self):
 11         print('取款')
 12 
 13     def withdraw(self):
 14         self.__card()
 15         self.__auth()
 16         self.__input()
 17         self.__print_bill()
 18         self.__take_money()
 19 
 20 a=ATM()
 21 a.withdraw()
 22 
 23 # 運行結果為
 24 插卡
 25 用戶認證
 26 輸入取款金額
 27 打印賬單
 28 取款
View Code

好了,明確了封裝的意義,下面我們來看看封裝的具體過程以及如何使用封裝大法。以下內容轉自https://www.cnblogs.com/wupeiqi/p/4493506.html
封裝,顧名思義就是將內容封裝到某個地方,以后再去調用被封裝在某處的內容。
所以,在使用面向對象的封裝特性時,需要:

  • 先將內容封裝到某處
  • 從某處調用被封裝的內容

第一步:將內容封裝到某處

self 是一個形式參數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等於 obj1
                       
      當執行 obj2 = Foo('alex', 78 ) 時,self 等於 obj2
所以,內容其實被封裝到了對象 obj1 和 obj2 中,每個對象中都有 name 和 age 屬性,在內存里類似於下圖來保存


這里也說明普通屬性的值,是封裝到了對象中,而屬性名稱和方法,則封裝在類中。

第二步:從某處調用被封裝的內容

調用被封裝的內容時,有兩種情況:

  • 通過對象直接調用
  • 通過self間接調用

1、通過對象直接調用被封裝的內容
上圖展示了對象 obj1 和 obj2 在內存中保存的方式,根據保存格式可以如此調用被封裝的內容:對象.屬性名

  1 class Foo:
  2 
  3     def __init__(self, name, age):
  4         self.name = name
  5         self.age = age
  6 
  7 obj1 = Foo('wupeiqi', 18)
  8 print obj1.name    # 直接調用obj1對象的name屬性
  9 print obj1.age     # 直接調用obj1對象的age屬性
 10 
 11 obj2 = Foo('alex', 73)
 12 print obj2.name    # 直接調用obj2對象的name屬性
 13 print obj2.age     # 直接調用obj2對象的age屬性
View Code

2、通過self間接調用被封裝的內容
執行類中的方法時,需要通過self間接調用被封裝的內容

  1 class Foo:
  2 
  3     def __init__(self, name, age):
  4         self.name = name
  5         self.age = age
  6 
  7     def detail(self):
  8         print self.name
  9         print self.age
 10 
 11 obj1 = Foo('wupeiqi', 18)
 12 obj1.detail()  # Python默認會將obj1傳給self參數,即:obj1.detail(obj1),所以,此時方法內部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
 13 
 14 obj2 = Foo('alex', 73)
 15 obj2.detail()  # Python默認會將obj2傳給self參數,即:obj1.detail(obj2),所以,此時方法內部的 self = obj2,即:self.name 是 alex ; self.age 是 78
 16 
View Code

綜上所述,對於面向對象的封裝來說,其實就是使用構造方法將內容(屬性)封裝到 對象 中,然后通過對象直接或者self間接獲取被封裝的內容。

3.2 繼承

一下文字轉自https://www.cnblogs.com/wupeiqi/p/4493506.htmlhttp://www.cnblogs.com/linhaifeng/articles/7340153.html有改動
繼承,面向對象中的繼承和現實生活中的繼承相同,即:子可以繼承父的內容。繼承是一種創建新類的方式,新建的類可以繼承一個或多個父類(python支持多繼承),父類又可稱為基類或超類,新建的類稱為派生類或子類。子類會“”遺傳”父類的屬性和方法,從而解決代碼重用問題(多個子類共有的屬性可以提取到父類中以便重用,多方法也是屬性,后續會專門闡述屬性)。
例如:
       貓可以:喵喵叫、吃、喝、拉、撒
       狗可以:汪汪叫、吃、喝、拉、撒
       如果我們要分別為貓和狗創建一個類,那么就需要為 貓 和 狗 實現他們所有的功能,如下所示:

  1 class cat:
  2     def yell(self):
  3         print('MiaoMiao')
  4     def eat(self):
  5         print('eating')
  6     def shit(self):
  7         print('shitting')
  8     def pee(self):
  9         print('pee')
 10 
 11 class dog:
 12     def yell(self):
 13         print('WangWang')
 14     def eat(self):
 15         print('eating')
 16     def shit(self):
 17         print('shitting')
 18     def pee(self):
 19         print('pee')
View Code

上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現:
  動物:吃、喝、拉、撒
    貓:喵喵叫(貓繼承動物的功能)
    狗:汪汪叫(狗繼承動物的功能)

  1 class Animal():
  2     def __init__(self, name):
  3         self.name = name
  4     def eat(self):
  5         print('%s is eating' % self.name)
  6     def shit(self):
  7         print('%s is shitting' % self.name)
  8     def pee(self):
  9         print('%s is peing' % self.name)
 10 
 11 class Cat(Animal):
 12     def yell(self):
 13         print('MiaoMiao')
 14 
 15 class Dog(Animal):
 16     def yell(self):
 17         print('WangWang')
 18 
 19 cat1 = Cat('Baimao')
 20 dog1 = Dog('XiaoHuang')
 21 
 22 運行結果:
 23 Baimao is eating
 24 XiaoHuang is eating
 25 cat1.eat()
 26 dog1.eat()
View Code

所以,對於面向對象的繼承來說,其實就是將多個類共有的方法提取到父類中,子類僅需繼承父類而不必一一實現每個方法
注:除了子類和父類的稱謂,你可能看到過 派生類 基類 ,他們與子類和父類只是叫法不同而已。

那么問題來了,如果子類從父類集成了某個屬性,但是又稍有改動,怎么辦呢?這就不得不采用先繼承再重構的思路了,即先從父類繼承某一屬性,然后在自己的類中重新定義該屬性,實現重構。需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調用新增的屬性時,就以自己為准了,並且整個重構過程不影響父類。

  1 class Animal():
  2     owner = 'human'  #公有屬性
  3     def __init__(self, name):
  4         self.name = name
  5     def eat(self):
  6         print('%s is eating' % self.name)
  7     def shit(self):
  8         print('%s is shitting' % self.name)
  9     def pee(self):
 10         print('%s is peing' % self.name)
 11 
 12 class Cat(Animal):
 13     def yell(self):
 14         print('MiaoMiao')
 15     def eat(self): #繼承eat方法然后重構
 16         print('%s is eatting fish' % self.name)
 17     owner = 'Tom'): #繼承owner屬性然后重構
 18 
 19 class Dog(Animal):
 20     def yell(self):
 21         print('WangWang')
 22     def eat(self): #繼承eat方法然后重構
 23         print('%s is eatting bone' % self.name)
 24     owner = 'Jack' #繼承owner屬性然后重構
 25 
 26 cat1 = Cat('Baimao')
 27 dog1 = Dog('XiaoHuang')
 28 cat1.eat()
 29 dog1.eat()
 30 print(cat1.owner)
 31 print(dog1.owner)
 32 
 33 運行結果:
 34 Baimao is eatting fish
 35 XiaoHuang is eatting bone
 36 Tom
 37 Jack
View Code

這里還有一個問題,多繼承呢?是否可以繼承多個類?
如果繼承的多個類每個類中都定了相同的函數,那么那一個會被使用呢?
1、Python的類可以繼承多個類,Java和C#中則只能繼承一個類
2、Python的類如果繼承了多個類,那么其尋找方法的方式有兩種,分別是:深度優先和廣度優先

經典類和新式類,從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之后推薦的寫法,從寫法上區分的話,如果 當前類或者父類繼承了object類,那么該類便是新式類,否則便是經典類。
        

經典類多繼承栗子:

  1 class D:
  2 
  3     def bar(self):
  4         print 'D.bar'
  5 
  6 
  7 class C(D):
  8 
  9     def bar(self):
 10         print 'C.bar'
 11 
 12 
 13 class B(D):
 14 
 15     def bar(self):
 16         print 'B.bar'
 17 
 18 
 19 class A(B, C):
 20 
 21     def bar(self):
 22         print 'A.bar'
 23 
 24 a = A()
 25 # 執行bar方法時
 26 # 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中么有,則繼續去D類中找,如果D類中么有,則繼續去C類中找,如果還是未找到,則報錯
 27 # 所以,查找順序:A --> B --> D --> C
 28 # 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
 29 a.bar()
View Code

新式類多繼承栗子:

  1 class D(object):
  2 
  3     def bar(self):
  4         print 'D.bar'
  5 
  6 
  7 class C(D):
  8 
  9     def bar(self):
 10         print 'C.bar'
 11 
 12 
 13 class B(D):
 14 
 15     def bar(self):
 16         print 'B.bar'
 17 
 18 
 19 class A(B, C):
 20 
 21     def bar(self):
 22         print 'A.bar'
 23 
 24 a = A()
 25 # 執行bar方法時
 26 # 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中么有,則繼續去C類中找,如果C類中么有,則繼續去D類中找,如果還是未找到,則報錯
 27 # 所以,查找順序:A --> B --> C --> D
 28 # 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
 29 a.bar()
View Code

經典類:首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中么有,則繼續去D類中找,如果D類中么有,則繼續去C類中找,如果還是未找到,則報錯
新式類:首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中么有,則繼續去C類中找,如果C類中么有,則繼續去D類中找,如果還是未找到,則報錯
總結:經典類深度優先,新式類廣度優先
注意:在上述查找過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
           python3 中經典類和新式類都遵循廣度優先原則,2.x中的深度優先已經被廢棄

3.3 多態

目前對於多態的理解很不到位,查了一些資料,很多地方是輕貓淡寫,先暫時引用一段到目前為止覺得講述不錯的博客把,后續有深入理解了再更新補充。原出處http://www.cnblogs.com/luchuangao/p/6739557.html

很多人喜歡將多態多態性二者混為一談,然后百思不得其解,其實只要分開看,就會很明朗。
(1)多態

多態指的是一類事物有多種形態,(一個抽象類有多個子類,因而多態的概念依賴於繼承)

  1. 序列類型有多種形態:字符串,列表,元組
  2. 動物有多種形態:人,狗,豬
  1 #多態:同一種事物的多種形態,動物分為人類,豬類(在定義角度)
  2 class Animal:
  3     def run(self):
  4         raise AttributeError('子類必須實現這個方法')
  5 
  6 
  7 class People(Animal):
  8     def run(self):
  9         print('人正在走')
 10 
 11 class Pig(Animal):
 12     def run(self):
 13         print('pig is walking')
 14 
 15 
 16 class Dog(Animal):
 17     def run(self):
 18         print('dog is running')
 19 
 20 peo1=People()
 21 pig1=Pig()
 22 d1=Dog()
 23 
 24 peo1.run()
 25 pig1.run()
 26 d1.run()
View Code


(2)多態性

  • 什么是多態性

多態性是指具有不同功能的函數可以使用相同的函數名,這樣就可以用一個函數名調用不同內容的函數。在面向對象方法中一般是這樣表述多態性:向不同的對象發送同一條消息,不同的對象在接收時會產生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。

  1 #多態性:一種調用方式,不同的執行效果(多態性)
  2 def func(obj):
  3     obj.run()
  4 
  5 func(peo1)
  6 func(pig1)
  7 func(d1)
  8 
  9 
 10 # peo1.run()
 11 # pig1.run()
 12 
 13 
 14 # 多態性依賴於:繼承
 15 ##多態性:定義統一的接口,
 16 def func(obj): #obj這個參數沒有類型限制,可以傳入不同類型的值
 17     obj.run() #調用的邏輯都一樣,執行的結果卻不一樣
 18 
 19 func(peo1)
 20 func(pig1)
 21 
 22 func(d1)
View Code



  1 >>> def func(animal): #參數animal就是對態性的體現
  2 ...     animal.talk()
  3 ...
  4 >>> people1=People() #產生一個人的對象
  5 >>> pig1=Pig() #產生一個豬的對象
  6 >>> dog1=Dog() #產生一個狗的對象
  7 >>> func(people1)
  8 say hello
  9 >>> func(pig1)
 10 say aoao
 11 >>> func(dog1)
 12 say wangwang
View Code

綜上可以說,多態性是一個接口(函數func),多種實現(如people.talk())

  • 多態性的好處

其實大家從上面多態性的例子可以看出,我們並沒有增加上面新的知識,也就是說Python本身就是支持多態性的,這么做的好處是什么呢?
(1)增加了程序的靈活性
  以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用,如func(animal)
(2)增加了程序額可擴展性
  通過繼承animal類創建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用

  1 >>> class Cat(Animal): #屬於動物的另外一種形態:貓
  2 ...     def talk(self):
  3 ...         print('say miao')
  4 ...
  5 >>> def func(animal): #對於使用者來說,自己的代碼根本無需改動
  6 ...     animal.talk()
  7 ...
  8 >>> cat1=Cat() #實例出一只貓
  9 >>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能
 10 say miao
 11 
 12 '''
 13 這樣我們新增了一個形態Cat,由Cat類產生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1)
 14 '''
View Code

總結:
多態:同一種事物的多種形態,動物分為人類,豬類(在定義角度) 多態性:一種調用方式,不同的執行效果(多態性)

 

寫在最后:
初學面向對象,還有很多基礎概念和重要知識待續,因篇幅所限,另外開一篇博客繼續進階把~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM