1. 程式人生 > >第038講:類和物件:繼承

第038講:類和物件:繼承

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

上節課的課後習題我們試圖模擬一個場景,裡邊有一隻烏龜和十條魚,烏龜通過吃魚來補充體力,當烏龜體力消耗殆盡或者魚吃光時遊戲結束。

現在我們想擴充套件遊戲,給魚類進行細分,有金魚(Goldfish),鯉魚(Carp),三文魚(Salmon)和鯊魚(Shark)。那麼我們就在思考一個問題:能不能不要每次都重頭到尾重新定義一個新的魚類呢?因為我們知道大部分魚的屬性和方法是相似的。如果說有一種機制可以讓這些相似的東西得以自動傳遞,我們不用每次都自己動手寫,那就方便快捷的多了。這種機制就是我們今天要講解的繼承。

語法:class 類名(父類名):

被繼承類被稱為基類、父類或超類,繼承者被稱為子類,一個子類可以繼承它的父類的任何屬性和方法。舉例說明:

>>> class Parent:
	def hello(self):
		print("正在呼叫父類的方法")

		
>>> class Child(Parent):
	pass

>>> p = Parent()
>>> p.hello()
正在呼叫父類的方法
>>> c = Child()
>>> c.hello()
正在呼叫父類的方法

需要注意的是:如果子類中定義與父類同名的方法或屬性,則會自動覆蓋父類對應的方法和屬性。

>>> class Parent:
	def hello(self):
		print("正在呼叫父類的方法")

	
>>> class Child(Parent):
	def hello(self):
		print("正在呼叫子類的方法")

		
>>> c = Child()
>>> c.hello()
正在呼叫子類的方法
>>> p = Parent()
>>> p.hello()
正在呼叫父類的方法

但是,這裡覆蓋的是子類例項化物件裡面的方法而已,對父類的方法沒有影響。

現在大家一起動手寫一下剛才提到的魚類擴充套件的例子。

這是一個有錯誤的程式程式碼:

import random as r
class Fish:
        def __init__(self):
                self.x = r.randint(0, 10)
                self.y = r.randint(0, 10)
        def move(self):
                self.x -= 1
                print("我的位置是:", self.x, self.y)
class Goldfish(Fish):
        pass
class Shark(Fish):
        def __init__(self):
                self.hungry = True
        def eat(self):
                if self.hungry:
                        print("吃貨的夢想就是天天有吃的^_^")
                        self.hungry = False
                else:
                        print("太撐了,吃不下了")

但是上面的程式是存在錯誤的。如下:

>>> fish = Fish()
>>> fish.move()
我的位置是: 2 9
>>> goldfish = Goldfish()
>>> goldfish.move()
我的位置是: -1 7
>>> shark = Shark()
>>> shark.eat()
吃貨的夢想就是天天有吃的^_^
>>> shark.move()
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    shark.move()
  File "C:/Users/XiangyangDai/Desktop/上課程式碼/38-1.py", line 7, in move
    self.x -= 1
AttributeError: 'Shark' object has no attribute 'x'

我們在呼叫shark.move()方法的時候會報錯,這是為什麼呢?錯誤資訊告訴我們,Shark 物件沒有 x 的屬性,Goldfish 和 Shark 都是繼承Fish,Fish是有x的屬性的,但是Shark重寫了 __init__(self)方法,子類重寫了父類的方法,就會把父類的方法給覆蓋。要解決這個問題的話,我們應該在Shark 的類裡面重寫 __init__(self)方法的時候先呼叫父類的__init__(self),實現這樣子的繼承總共有兩種技術。

  • 呼叫未繫結的父類方法(該方法不重要)
import random as r
class Fish:
        def __init__(self):
                self.x = r.randint(0, 10)
                self.y = r.randint(0, 10)
        def move(self):
                self.x -= 1
                print("我的位置是:", self.x, self.y)
class Goldfish(Fish):
        pass
class Shark(Fish):
        def __init__(self):
                #呼叫未繫結的父類方法
                Fish.__init__(self)
                self.hungry = True
        def eat(self):
                if self.hungry:
                        print("吃貨的夢想就是天天有吃的^_^")
                        self.hungry = False
                else:
                        print("太撐了,吃不下了")
>>> shark = Shark()
>>> shark.move()
我的位置是: 6 0

這樣就不會報錯了,需要注意的是, Fish.__init__(self) 中的 self 是呼叫它的父類的方法,但是這個 self 是子類的例項物件。

就相當於:Fish.__init__(shark)。實際上,在上面出錯的程式程式碼執行之後,我們輸入下面的語句可是可以的:這裡就相當於重新進行了一次初始化。

>>> shark = Shark()
>>> Fish.__init__(shark)
>>> shark.move()
我的位置是: 6 1
  • 使用 super 函式(完美方法)

使用super 函式能夠幫我們自動找到父類的方法,而且還會為我們傳入 self 引數,super 函式可以完美的替換上述的方法。

import random as r
class Fish:
        def __init__(self):
                self.x = r.randint(0, 10)
                self.y = r.randint(0, 10)
        def move(self):
                self.x -= 1
                print("我的位置是:", self.x, self.y)
class Goldfish(Fish):
        pass
class Shark(Fish):
        def __init__(self):
                #使用super函式
                super().__init__()
                self.hungry = True
        def eat(self):
                if self.hungry:
                        print("吃貨的夢想就是天天有吃的^_^")
                        self.hungry = False
                else:
                        print("太撐了,吃不下了")
>>> shark = Shark()
>>> shark.move()
我的位置是: 6 2

super().__init__(),super 函式的超級之處就在於你不用給定任何父類的名字,如果繼承有多重繼承或者父類的名字太過複雜的時候,也不用給出父類的名字,就可以自動幫你一層一層的找出它所有父類裡面對應的方法,由於你不需要給出父類的名字,也就意味著如果你要改變類的繼承關係,你只需要修改 class Shark(Fish): 裡面的父類的名字即可。

多重繼承:就是同時繼承多個父類的屬性和方法。

語法:class 類名(父類1名,父類2名........):

>>> class Base1:
	def foo1(self):
		print("我是foo1,我為Base1代言...")

		
>>> class Base2:
	def foo2(self):
		print("我是foo2,我為Base2代言...")

		
>>> class C(Base1, Base2):
	pass

>>> c = C()
>>> c.foo1()
我是foo1,我為Base1代言...
>>> c.foo2()
我是foo2,我為Base2代言...

多重繼承可以同時繼承多個父類的屬性和方法,但是多重繼承很容易導致程式碼混亂,所以當你不確定你真的必須要使用多重繼承的時候,請儘量避免使用

測試題

0. 繼承機制給程式猿帶來最明顯的好處是?

答:可以偷懶,據說這是每一個優秀程式猿的夢想!

如果一個類 A 繼承自另一個類 B,就把這個 A 稱為 B 的子類,把 B 稱為 A 的父類、基類或超類。繼承可以使得子類具有父類的各種屬性和方法,而不需要再次編寫相同的程式碼(偷懶)。

在子類繼承父類的同時,可以重新定義某些屬性,並重寫某些方法,即覆蓋父類的原有屬性和方法,使其獲得與父類不同的功能。另外,為子類追加新的屬性和方法也是常見的做法。

1. 如果按以下方式重寫魔法方法 __init__,結果會怎樣?

class MyClass:
    def __init__(self):
        return "I love FishC.com!"

答:會報錯,因為 __init__ 特殊方法不應當返回除了 None 以外的任何物件。

>>> myClass = MyClass()
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    myClass = MyClass()
TypeError: __init__() should return None, not 'str'

2. 當子類定義了與相同名字的屬性或方法時,Python 是否會自動刪除父類的相關屬性或方法?

答:不會刪除!Python 的做法跟其他大部分面向物件程式語言一樣,都是將父類屬性或方法覆蓋,子類物件呼叫的時候會呼叫到覆蓋後的新屬性或方法,但父類的仍然還在,只是子類物件“看不到”。

3. 假設已經有鳥類的定義,現在我要定義企鵝類繼承於鳥類,但我們都知道企鵝是不會飛的,我們應該如何遮蔽父類(鳥類)中飛的方法?

答:覆蓋父類方法,例如將函式體內容寫 pass,這樣呼叫 fly 方法就沒有任何反應了。

class Bird:
        def fly(self):
                print("Fly away!")

class Penguin(Bird):
        def fly(self):
                pass

>>> bird = Bird()
>>> penguin = Penguin()
>>> bird.fly()
Fly away!
>>> penguin.fly()

4. super 函式有什麼“超級”的地方?

答:super 函式超級之處在於你不需要明確給出任何基類的名字,它會自動幫您找出所有基類以及對應的方法。由於你不用給出基類的名字,這就意味著你如果需要改變了類繼承關係,你只要改變 class 語句裡的父類即可,而不必在大量程式碼中去修改所有被繼承的方法。

5. 多重繼承使用不當會導致重複呼叫(也叫鑽石繼承、菱形繼承)的問題,請分析以下程式碼在實際程式設計中有可能導致什麼問題?

class A():
    def __init__(self):
        print("進入A…")
        print("離開A…")

class B(A):
    def __init__(self):
        print("進入B…")
        A.__init__(self)
        print("離開B…")
        
class C(A):
    def __init__(self):
        print("進入C…")
        A.__init__(self)
        print("離開C…")

class D(B, C):
    def __init__(self):
        print("進入D…")
        B.__init__(self)
        C.__init__(self)
        print("離開D…")

答:多重繼承容易導致重複呼叫問題,下邊例項化 D 類後我們發現 A 被前後進入了兩次(有童鞋說兩次就兩次憋,我女朋友還不止呢……)。
這有什麼危害?我舉個例子,假設 A 的初始化方法裡有一個計數器,那這樣 D 一例項化,A 的計數器就跑兩次(如果遭遇多個鑽石結構重疊還要更多),很明顯是不符合程式設計的初衷的(程式應該可控,而不能受到繼承關係影響)。

>>> d = D()
進入D…
進入B…
進入A…
離開A…
離開B…
進入C…
進入A…
離開A…
離開C…
離開D…

為了讓大家都明白,這裡只是舉例最簡單的鑽石繼承問題,在實際程式設計中,如果不注意多重繼承的使用,會導致比這個複雜N倍的現象,除錯起來不是一般的痛苦……所以一定要儘量避免使用多重繼承。

6. 如何解決上一題中出現的問題?

答:super 函式再次大顯神威。

class A():
    def __init__(self):
        print("進入A…")
        print("離開A…")

class B(A):
    def __init__(self):
        print("進入B…")
        super().__init__()
        print("離開B…")
        
class C(A):
    def __init__(self):
        print("進入C…")
        super().__init__()
        print("離開C…")

class D(B, C):
    def __init__(self):
        print("進入D…")
        super().__init__()
        print("離開D…")

>>> d = D()
進入D…
進入B…
進入C…
進入A…
離開A…
離開C…
離開B…
離開D…

動動手

0. 定義一個點(Point)類和直線(Line)類,使用 getLen 方法可以獲得直線的長度。

提示:

  • 設點 A(X1,Y1)、點 B(X2,Y2),則兩點構成的直線長度 |AB| = √((x1-x2)2+(y1-y2)2)
  • Python 中計算開根號可使用 math 模組中的 sqrt 函式
  • 直線需有兩點構成,因此初始化時需有兩個點(Point)物件作為引數
import math

class Point():
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def getX(self):
        return self.x

    def getY(self):
        return self.y

class Line():
    def __init__(self, p1, p2):
        self.x = p1.getX() - p2.getX()
        self.y = p1.getY() - p2.getY()
        self.len = math.sqrt(self.x*self.x + self.y*self.y)

    def getLen(self):
        return self.len

>>> p1 = Point(1, 1)
>>> p2 = Point(4, 5)
>>> line = Line(p1, p2)
>>> line.getLen()
5.0

1. 展示一個你的作品:你已經掌握了 Python 大部分的基礎知識,要開始學會自食其力了!

請花一個星期做一個你能做出來的最好的作品(可以是遊戲、應用軟體、指令碼),使用上你學過的任何東西(類,函式,字典,列表……)來改進你的程式。"

答:這一次,我不打算提醒你要具體做點什麼和怎麼做,你需要自己來想創意。試著下手吧,程式設計就是解決問題的過程,這就意味著你要嘗試各種可能性,進行實驗,經歷失敗,然後丟掉你做出來的東西重頭開始!

當你被某個問題卡住的時候,你可以到論壇尋求幫助,把你的程式碼貼出來給其他魚油看,爭取得到別人的建議並持續修改你的程式碼,直到它讓你滿意為止!

相關推薦

038物件繼承

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式! 上節課的課後習題我們試圖模擬一個場景,裡邊有一隻烏龜和十條魚,烏龜通過吃魚來補充體力,當烏龜體力消耗殆盡或者魚吃光時遊戲結束。 現在我們想擴充套件遊戲,給魚類進行細分,有金魚(Goldfis

036物件給大家介紹物件

目錄 0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式! 測試題 0. 物件中的屬性和方法,在程式設計中實際是什麼? 1. 類和物件是什麼關係呢? 2. 如果我們定義了一個貓類,那你能想象出由“貓”類例項化的物件有哪些? 3. 類的定義有些時候或

037物件面向物件程式設計

目錄 0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式! 測試題 0. 以下程式碼體現了面向物件程式設計的什麼特徵? 1. 當程式設計師不想把同一段程式碼寫幾次,他們發明了函式解決了這種情況。當程式設計師已經有了一個類,而又想建立一個非常相近的新類,他們

039物件拾遺

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式! (一)組合 上節課我們學習了繼承和多繼承,但是我們有時候發現,有些情況你用繼承也不合適,用多繼承也不是,例如:現在現在要求定義一個類,叫水池,水池裡要有烏龜和魚。那大家就很苦惱了,用多繼承就顯得很奇葩了,因為如

040物件一些相關的BIF

目錄 0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式! 測試題 0. 如何判斷一個類是否為另一個類的子類? 1. 如何判斷物件 a 是否為 類 A 的例項物件? 2. 如何優雅地避免訪問物件不存在的屬性(不產生異常)? 3. Python 的一

Scala 程式設計—七節物件(二)

前言:         類和物件第二節,主要介紹:單例物件、伴生物件與伴生類、apply方法、抽象類 1.單例物件 Java語言中,如果想直接使用類名點的方式呼叫方法或屬性,直接用static修飾即可。但Scala語言不支援靜態成員,而提供了object物件,這

Scala入門到精通——六節物件(一)

本節主要內容 1 類定義、建立物件 2 主構造器 3 輔助構造器 類定義、建立物件 //採用關鍵字class定義 class Person { //類成員必須初始化,否則會報錯 //這裡定義的是一個公有成員 var name:Strin

Scala入門到精通——七節物件(二)

本節主要內容 單例物件 伴生物件與伴生類 apply方法 應用程式物件 抽象類 單例物件 在某些應用場景下,我們可能不需要建立物件,而是想直接呼叫方法,但是Scala語言並不支援靜態成員,Scala通過單例物件來解決該問題。單例物件的建立方式如下:

《Java從入門到失業》四章物件(4.2)String

4.2String類        這一節,我們學習第一個類:String類。String翻譯成漢語就是“字串”,是字元的序列。我們知道,在Java中,預設採用Unicode字符集,因此字串就是Unicode字元的序列。例如字

《Java從入門到失業》四章物件(4.4)方法引數及傳遞

4.4方法引數及傳遞        關於這個知識點,我想了很久該不該在這裡闡述。因為這個知識點稍微有點晦澀,並且就算不了解也不影響用Java編寫程式碼。不過筆者剛開始工作的時候,就是因為這塊內容沒有過多的關注,以至於相當於長一段時間對這塊內容都模糊不

《Java從入門到失業》四章物件(4.5)

4.5包        前面我們已經聽過包(package)這個概念了,比如String類在java.lang包下,Arrays類在java.util包下。那麼為什麼要引入包的概念呢?我們思考一個問題:java類庫提供了上千個類,我們很難完全記住他們

《Java從入門到失業》四章物件(4.6)路徑

4.6類路徑 4.6.1什麼是類路徑        前面我們討論過包,知道位元組碼檔案最終都會被放到和包名相匹配的樹狀結構子目錄中。例如上一節的例子:     其實類還有一種存放方式,就是可以歸檔到一個jar檔案中,jar檔案其實就是把位元

Java基礎 實驗二物件

1.實驗目的 掌握類的宣告、物件的建立、方法的定義和呼叫、建構函式的使用。 2.實驗內容 1)定義一個表示學生資訊的類Student,要求如下: ① 類Student的成員變數:       sNO 表示學號;      

Scala學習筆記(二)物件

object object 是隻有一個例項的類。它的定義與Java中的class類似,如: // 單例物件 object AppEntry { def main(args: Array[String]): Unit = { print("Hello World!") } }

Java筆記物件

Java類和物件 類是在程式設計中經過編寫的,擁有成員變數和一些方法的一種資料型別,可以由自己通過聯絡現實中的事物進行編寫,關鍵字是class,一個Java原始檔只能含有一個public類,且該類名稱與檔名相同。類是對現實中具體事物的一種表示,並且需藉由類建立

面向物件思想物件、封裝、建構函式

面向物件的特點: 面向物件是一種更符合我們思考習慣的思想,它可以將複雜的事件簡單化,並將我們從執行者變成指揮者。 面向過程-強調步驟 面向過程-強調物件,這裡的物件就是洗衣機。 面向物件的語

《零基礎入門學習Python》(36)--物件面向物件程式設計的相關知識

前言 Python3 面向物件 Python從設計之初就已經是一門面向物件的語言,正因為如此,在Python中建立一個類和物件是很容易的。本章節我們將詳細介紹Python的面向物件程式設計。 如果你以前沒有接觸過面向物件的程式語言,那你可能需要先了解一些面嚮物件語言的一

《零基礎入門學習Python》(40)物件一些與物件相關的BIF

知識點: 1.issubclass(class,classinfo)#class是classinfo的子類則返回True,相反則返回False 注意: 1.這種檢查是非嚴肅性的檢查,他會把自身當成自身的子類 2.classinfo可以是類物件組成的元組,只要cla

python cookbook學習筆記十六物件(1)

我們經常會對列印一個物件來得到物件的某些資訊。 class pair:     def __init__(self,x,y):         self.x=x         self.y=y if __name__=='__main__':     p=pair

Java 7物件(域、引數、初始化)

面向物件程式設計:每個物件包含對使用者公開的特定功能部分和隱藏的實現部分,在OOP中不必關心物件的具體實現,OOP更看重資料(結構),而不是具體演算法。 封裝(資料隱藏):將資料和行為組合在一個包裡,並對物件的使用者隱藏資料的實現方式,封裝的關鍵是絕不能讓其他類直接訪問例項