OOP — Python Inheritance 關於物件導向的繼承

Sean Yeh
Python Everywhere -from Beginner to Advanced
15 min readJan 26, 2022

--

Wanaka, New Zealand, photo by Sean Yeh

「繼承」一詞是個法律上的用語,常常出現在家中關於財產分配的問題上。例如民法有一整篇章(第五編 第1138條以下)在說明關於遺產繼承的問題。除此之外,我們會想到的例子是,當小孩子出生後不論在外型、長相或者是個性上漸漸的與生父母有些相似性,我們會說這小孩子繼承(更常用的詞是「遺傳」)了他父母的一些特徵。那麼我們為何在這裡討論「繼承」這個名詞?難道Python程式也會繼承?沒錯,我們也可以在Python程式裡面找到「繼承」(Inheritance)這個名詞。

類別可以繼承(Inheritance)

在Python程式中,繼承(Inheritance)是讓某個類別從另外一個類別承繼某些特性的功能。在這兩的類別間傳承的東西,正式類別中所宣告的資料與方法。繼承者被稱為子類別,而被繼承者則被稱為父類別。這概念比較屬於前述「繼承」中後者的定義。

為何需要類別繼承?

為何在Python程式中需要有這個概念?想要暸解繼承就需要做一個反向的假設,先來看看下面的例子。我們先假設Python程式中沒有繼承這種方式,在沒有繼承的狀況下我們要如何處理這個問題?

如果Python沒有繼承

如果我們想要分別設定三個類型的人,一般人、員工以及學生。這三種人除了都有姓與名之外,員工還具備員工編號以及薪資的資料,而學生則另外有學號以及成績。

依照上面的假設,我們可以做成下面三個類別Class,分別是Person,Employee與Student。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
class Employee:
def __init__(self,first,last, employee_id,salary):
self.firstname = first
self.lastname = last

self.employee_id = employee_id
self.salary = salary
class Student:
def __init__(self,first,last, student_id,grades):
self.firstname = first
self.lastname = last

self.student_id = student_id
self.grades = grades

觀察上面的程式碼,你會發現在這做這三個類別時似乎做了幾次程式碼的「複製貼上」,整個程式碼看起來攏長且容易出錯。過沒多久,如果我們又產生新需求需要再增加一個角色。這時候如果像上面一樣一直增加類別,是不是覺得這樣子寫程式好像有點累贅的感覺?

難道我們不能用更簡潔更DRY(Don’t Repeat Yourself)的方式來處理這個問題嗎?

有繼承的Python

所謂的DRY就是當我們寫程式時,若用一行可以解決的話,就不要重複寫第二行。因此,如果程式碼中有了繼承的概念,在處理上面類別的問題時就會變成這樣:

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
class Employee(Person):
def __init__(self,first,last,employee_id,salary):
super().__init__(first,last)
self.employee_id = employee_id
self.salary = salary
class Student(Person):
def __init__(self,first,last,student_id,grades):
super().__init__(first,last)
self.student_id = student_id
self.grades = grades

在上面的程式碼裡面,我們不再做程式碼「複製貼上」的工作。員工類別與學生類別,繼承一般人類別。因此,程式碼變得較為清爽,未來在維護上也比較省事。

繼承的語法

在繼承中可以分為父類別(superclass)與子類別(subclass)兩個概念。父類別在建立的過程與一般類別的寫法沒有什麼不同,但在父類別裡面,我們要定義的屬性(attribute)與方法(method)未來可以提供子類別使用。

在繼承中可以分為父類別(superclass)與子類別(subclass)兩個概念。父類別在建立的過程與一般類別的寫法沒有什麼不同,但在父類別裡面,我們要定義的屬性(attribute)與方法(method)未來可以提供子類別使用。

而在子類別的地方,需要在括號中填入父類別的名稱,以標示繼承了父類別的屬性與方法。

class 類別名稱(父類別名稱):    
//程式區塊

回到上面一般人與員工的例子(先不看學生的部分)。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
class Employee(Person):
def __init__(self,first,last,employee_id,salary):
super().__init__(first,last)
self.employee_id = employee_id
self.salary = salary

員工(Employee)類別乃繼承了一般人(Person)類別。因此,可以在員工(Employee)類別後面的括號裡面看到Person。表示員工(Employee)類別繼承了Person父類別的屬性與方法。

我們可以透過isinstance的方法來檢查是體是否是源自父類別。

# 建立A實體
A= Person('John','Law')
# 建立B實體
B= Employee('Anna','Sue',100,45000)

使用Preson與Employee類別分別見建立A、B兩個實體。

先印出A實體是否為Person的一個實體化?答案應該很明顯,會得到True。

print(isinstance(A,Person))

另外,測試B實體是否為Person的一個實體化?答案也是True。

print(isinstance(B,Employee))

以上應該沒有疑義。

接下來就可以測試看看B是否為Peron的實體化?

print(isinstance(B,Person))

答案一樣是True。因此可以得知B不僅是Employee類別的實體化,也是Peron類別的實體化。Employee類別繼承了Peron類別。

方法改寫 Method Overriding

前面提到的類別繼承,我們可以從父類別繼承屬性與方法。我們是否可以在子類別中將父類別中繼承來的方法進行修改?這個問題稱為方法改寫(Method Overriding)。

延續上面的Person例子,我們在父類別裡面增加一個fullname方法,透過這個方法可以將這個人的名字列印出來顯示在螢幕上。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
def fullname(self):
print(f'{self.firstname} {self.lastname}')
class Employee(Person):
def __init__(self,first,last,employee_id,salary):
super().__init__(first,last)
self.employee_id = employee_id
self.salary = salary

如果子類別Employee要改變繼承自父類別的結果時,我們可以在子類別中同樣加上一個fullname方法來改寫(Overriding)結果。如下,我們在子類別Employee中加上一個fullname方法,該方法的內容與父類別Person中的內容稍有不同。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
def fullname(self):
print(f'{self.firstname} {self.lastname}')
class Employee(Person):
def __init__(self,first,last,employee_id,salary):
super().__init__(first,last)
self.employee_id = employee_id
self.salary = salary
def fullname(self):
return f'員工 {self.firstname} {self.lastname}'

執行看看,可以發現p.fullname()與e.fullname()除了名字不一樣外,e.fullname()還增加了「員工」兩個字。

p = Person('Anny','Peritz')
e = Employee('Anna','Sue',100,45000)
p.fullname()
e.fullname()

這就是因為我們透過方法改寫(Method Overriding)的方式修改了繼承自父元件的方法。

使用Super()改寫屬性

上面的Employee._init__在Python中有一個寫法,就是鼎鼎大名的super。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
def info(self):
print(f'{self.firstname} {self.lastname}')
class Employee(Person):
def __init__(self,first,last,employee_id,salary):
super().__init__(first,last)
self.employee_id = employee_id
self.salary = salary

def info(self):
print(f'{self.firstname} {self.lastname}')
print(f'{self.employee_id} {self.salary}')

透過super()可以讓子類別繼承父類別中的屬性並在後面加上子類別專有的屬性。在上面的例子裡面我們看到 super().__init__(first,last),因此子類別可以繼承父類別的first與last。並且子類別Employee中還保有特有的employee_idsalary屬性。

此外,我們也可以把super()用在方法繼承上面。例如,可以把Employee中的info方法改為使用super()來表示。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
def info(self):
print(f'{self.firstname} {self.lastname}')
class Employee(Person):
def __init__(self,first,last,employee_id,salary):
super().__init__(first,last)
self.employee_id = employee_id
self.salary = salary

def info(self):
super().info()
print(f'{self.employee_id} {self.salary}')

如此程式碼更為簡潔。未來當我們需要修改info的內容時,可以直接從Person裡面的info方法進行修改,這樣子所有繼承父類別的子類別都會一並的受到影響,或者是只在Employee裡面的info方法裡面添加新的程式碼,這樣子只有Employee子類別單獨受到影響,不影響其他的子類別與父類別。

實例

下面是個會員費用的例子。

這個例子一樣衍生自前面的Person類別。在Person類別中除了firstname與lastname屬性外,還有info方法。

class Person:
def __init__(self,first,last):
self.firstname = first
self.lastname = last
def info(self):
print(f'{self.firstname} {self.lastname}')

接著從Person延伸出Member類別,繼承自Person類別。在Member類別中修改繼承來的屬性增加了member_id與fee,並且修改了info方法。另外,添加了Member自己的get_discount方法。

class Member(Person):
def __init__(self,first,last,member_id,fee):
super().__init__(first,last)
self.member_id = member_id
self.fee = fee
def info(self):
super().info()
print(f'會員編號:{self.member_id},金額:{self.fee}')

def get_discount(self):
self.fee = self.fee - 0.1*self.fee
print(f'折扣後{self.fee}元')

先試試看上面的程式,建立john實體:

john = Member('John','Tyler',2322,1500)
john.get_discount()
john.info()

會得到下面的結果:

不同等級的會員

我們可以再延伸下去,替會員增加不同等級(一般卡、銀卡、金卡會員)。

一般卡、銀卡與金卡會員都繼承於Member類別。其中一般卡(NormalMember)另外加上info方法。銀卡(SilverMember)與金卡會員(GoldenMember)除了加上info方法外,還修改從Member類別繼承來的get_discount方法。

class GoldenMember(Member):
def get_discount(self):
self.fee = self.fee - 0.3*self.fee
print(f'折扣後{self.fee}元')
def info(self):
super().info()
print("您是金卡會員")
class SilverMember(Member):
def get_discount(self):
self.fee = self.fee - 0.2*self.fee
print(f'折扣後{self.fee}元')
def info(self):
super().info()
print("您是銀卡會員")
class NormalMember(Member):
def info(self):
super().info()
print("您是我們的會員")

執行上面的程式碼之後,可以得到下面的結果:

結語

使用Python繼承的目的是為了要讓程式碼更容易規劃與維護,透過這種方式讓我們寫的程式碼可以更加的DRY,達到簡潔的目的。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。