Skip to main content

Python 繼承介紹

Python 繼承

Inheritance Syntax

class Child (ParentA, ParentB):
pass

Python 允許 多重繼承
上方範例為 Child 同時繼承 ParentA, ParentB。
若父類別們同時具有相同的變數時,
子類別只取定義在左側的實作。

Inheritance Syntax to Linear Inheritabce  

object
  ^
  |
ParentB
  ^
  |
ParentA
  ^
  |
Child
  • 註:上方轉換後的結構資訊可以由 Child.mro() 取得

也可想成 Child 先繼承 ParentA,再繼承 ParentB。沒上游後再繼承 object。
當將繼承語法拆解成上方線性結構後,
我們可以清楚看出 Method Resolution Order的原則:
由下而上 (上方為抽象類)
由左樹而右樹 (右側為抽象類)
遇到同名成員時左側優先 (覆寫優先)

一切都導因於:
接近塑模的類別優先
繼承的越下方越接近實體類別,所以先採用
而子類別可覆寫父類別成員,所以子類優先

Multiple Inheritance

[B1]  [B2]  [B3]   [B4]
 ^ ^ ^ ^
  \ / \ /
   \ / \ /
   [A1] [A2]
     ^ ^
      \ /
       \ /
        \ /
        MyClass

繼承語法中 :

多重繼承左側優先次序較高尋找繼承成員時,
由下而上, MyClass-> A -> B
由左而右。 A1 tree -> A2 tree
最終: MyClass > A1 > B1 > B2 > A2 > B3 > B4 > object
  • hint: 重組成 線性單一繼承結構以便於理解

    [<class 'main.MyClass'>, <class 'main.A1'>, <class 'main.B1'>, <class 'main.B2'>, <class 'main.A2'>, <class 'main.B3'>, <class 'main.B4'>, <class 'object'>]

Diamond Pattern Inheritance: 鑽石繼承

  • TODO insert Diamond_Pattern_Inheritance.svg

    Python3+ 查詢順序 : 由下而上,由左而右 與多重繼承相同
    但當遇到鑽石相交時,則會先載入反向有子類到父類的共祖 tree,再依序走回簡單多繼承路線。
    Python2 規則不同(不敘述避免混淆)

Diamond Pattern Inheritance example: oop.inheritance.diamond.py

class Eukaryote:  # 真核生物
pass

class CoAncestor(Eukaryote): # 共祖
pass

class CellWall(): # 細胞壁
pass

class Chloroplast(): # 葉綠體
pass

class Centriole(): # 中心粒
pass

class Cilium(): # 纖毛
pass

class Animal(Centriole, CoAncestor, Cilium): # 動物
pass

class Plant(CellWall, CoAncestor, Chloroplast): # 植物
pass


class VegetativeState(Animal, Plant): # 植物人
pass

# Plant 的共祖 tree: plant > CoAncestor > Eukaryote
# 當 VegetativeState 走到上層, Animal.CoAncestor 時會先解析 (CoAncestor 共祖 tree, 包含項下的 Plant 子類)
# VegetativeState > Animal Tree >> CoAncestor 共祖 tree > Plant Tree


print(VegetativeState.mro())
# [<class '__main__.VegetativeState'>,
# <class '__main__.Animal'>,
# <class '__main__.Centriole'>,
#
# <class '__main__.Plant'>, ## CoAncestor Tree
# <class '__main__.CellWall'>, ## 建構 Plant Tree 時 CellWall 優先於 CoAncestor Tree
# <class '__main__.CoAncestor'>,
# <class '__main__.Eukaryote'>,
#
# <class '__main__.Cilium'>,
### 建構 Animal Tree 剩餘部分 (Centriole > CoAncestor Tree > Cilium)
#
# <class '__main__.Chloroplast'>,
### 建構 Plant Tree 剩餘部分
#
# <class 'object'>]

MRO : Method Resolution Order

Python 中以 super() 呼叫上層時的查詢路徑稱為 MRO (Method Resolution Order)。

  • Python3+ 的查詢原則是
    • 廣度優先
    • 由左到右
    • C3 演算法,以確保一個 只能被查一次。
  • ClassName.mro() 可用來查詢實際的 Method Resolution Path。

Override Methods

方法覆寫 (Overriding): 指的是父類別與子類別各自具有同名方法實作。
實際採用何者,則看建構時是以哪一個 Type 建構。
不需像 Java 額外標註

class Animal:
def voice(self):
return '......'

class Dog(Animal):
def voice(self):
return 'Bark!'

class Cat(Animal):
def voice(self):
return 'Mew~'

animal = Animal()
dog = Dog()
cat = Cat()

print(animal.voice()) # ......
print(dog.voice()) # Bark!
print(cat.voice()) # Mew~

Overload Methods

Python 中沒有 Overload methods。
Python 語言本身便預設提供
variable-arguments, key-word-arguments, default value 等語法
可達到其他語言中的 overloading 功能。
需注意的是,Python 允許繼承關係間同名變數存在,而 Method Name 也是一種變數名,
依據 MRO 順序,最接近實例(子類別)的同名方法為顯性。

繼承結構下 Python 建構子

Variable Argument 與 key-word Argument 的特性不多說,本身即是一種 Overloading 變形。

Python 語法中可以使用 @classmethod 特性,在類別方法中經由 cls argument 來呼叫 __init__() 建構子,
這個方法類似 Java 語言中的 this() 來呼叫多載的建構子。
使用這個 Pattern 可以讓程式碼具有商業意義。

Java 語言中在子類別中以 super() 來呼叫父類別建構子的語法。
Python 語言也有相同的 super() 語法,但建議建議僅在 單一繼承 情境下使用。
多重繼承情境則建議依據 Type 來呼叫父類建構子。

super.constructor.py: 單一繼承範例  

  • 還是建議採用 ParentName.__init__(self) 這語法
  • 注意: 須 slef argument

class Person:
def __init__(self, role_name):
self.role_name = role_name
print('-Person-')

def print_role(self):
print(self.role_name)


class Writer(Person):
def __init__(self, role_name, expertise):
Person.__init__(self, role_name) ## 建議使用
# super().__init__(role_name) ## 限單一繼承時使用
# super(Writer, self).__init__(role_name) ## 依據 MRO order ,建議單一繼承時使用。
self.expertise = expertise
print("-Writer-")

def print_expertise(self):
print(self.expertise)

class Cook(Person):
def __init__(self, role_name, expertise):
Person.__init__(self, role_name) ## 建議使用
self.expertise = expertise
print("-Cooking-")

jk_rowling = Writer("Novel Writer", 'Writing')

jk_rowling.print_role()
# Novel Writer
jk_rowling.print_expertise()
# Writing

super.constructor.py: 多繼承範例

  • 還是建議採用 ParentName.__init__(self) 這語法,因為可以自行決定使用順序。
  • 注意: 須帶 slef argument

### Multiple Parent Example
class Scissor:
def __init__(self, action):
print("-Scissors-", action)
class Screwdriver:
def __init__(self, action):
print("-Screwdriver-", action)

class Knife:
def __init__(self, action):
print("-Knife-", action)

class SwissKnife(Screwdriver, Scissor, Knife ):
def __init__(self):
# super(Knife, self).__init__() # 只抓 self 與 MRO 中 self 的次一個
# super.__init__() # 多重繼承下無法辨別

Scissor.__init__(self, 'snipping') ## 建議使用, 可自訂 super 的 order
Knife.__init__(self, 'cutting')
Screwdriver.__init__(self, 'drilling')

print('-SwissKnife-')

tool = SwissKnife()

繼承下的 super() 功能

  • 用在單一繼承的情境下。
  • 可用來呼叫
    • 父類別建構子 在上方範例中已介紹。
    • 父類別方法函數: ParentName.methodName(self)
  • 單用 Super() 語法時,若為 Multiple Inheritance,會因不知道確切指向哪個父類別而拋出錯誤。

super.method.py


class Scissor:
def __init__(self):
pass

def do(self, message):
print('Scissor ', message)


class Screwdriver:
def __init__(self):
pass

def do(self, message):
print('Screwdriver ', message)


class Knife:
def __init__(self):
pass

def do(self, message):
print('Knife ', message)


class SwissKnife(Screwdriver, Scissor, Knife):
def __init__(self):
pass

def do(self):
Screwdriver.do(self, "Drilling") # 建議方式
# super(Screwdriver, self).do("go") # by MRO. next is Scissor,不建議使用


print(SwissKnife.mro())
# [<class '__main__.SwissKnife'>, <class '__main__.Screwdriver'>, <class '__main__.Scissor'>, <class '__main__.Knife'>, <class 'object'>]

tool = SwissKnife()
tool.do()
# Screwdriver Drilling

查詢繼承與型別相關屬性

  • 以上方 SwissKnife 為例:

由 instance/class 來反查繼承相關屬性

  • 基本上 instance 可經由內建變數 __class__ 反查得到 instance 的 class type。繼承相關屬性便可由 Type 來取得。
Instance AttributesDescription
instance.__class__回傳 class
instance.__class__.__name__回傳 class name
instance.__class__.__bases__回傳 class 的上游繼承結構
instance.__class__.__base__回傳 class 的上一層父類別
instance.__class__.__mro__回傳 class 的繼承查找/函數查找路徑
instance.__class__.mro()等同於 instance.__class__.__mro__
instance.__dict__列出 instance 的所有 field/values
instance.__module__顯示 module 資訊
tool = SwissKnife()
tool.do()

print(tool.__class__) # <class '__main__.SwissKnife'>
print(tool.__class__.__name__) # SwissKnife
print(tool.__class__.__bases__) # (<class '__main__.Screwdriver'>, <class '__main__.Scissor'>, <class '__main__.Knife'>)
print(tool.__class__.__base__) # <class '__main__.Screwdriver'>
print(tool.__class__.__mro__) # (<class '__main__.SwissKnife'>, <class '__main__.Screwdriver'>, <class '__main__.Scissor'>, <class '__main__.Knife'>, <class 'object'>)

print(SwissKnife.mro()) # [<class '__main__.SwissKnife'>, <class '__main__.Screwdriver'>, <class '__main__.Scissor'>, <class '__main__.Knife'>, <class 'object'>]
print(Scissor.mro()) # [<class '__main__.Scissor'>, <class 'object'>]
print(SwissKnife.__dict__) # {'__module__': '__main__', '__init__': <function SwissKnife.__init__ at 0x0000014FCF402160>, 'do': <function SwissKnife.do at 0x0000014FCF4021F0>, '__doc__': None}

繼承結構與實例類別檢驗函數

  • isinstance(instance, type_or_tuple): 檢驗 instance 的 class
  • issubclass(Child, Parent_or_tuple): 檢驗繼承關係
    • 當給定的 Types 是 tuple 時,檢查的結果是任一符合變回傳 True。(if any match)

swissKnife = SwissKnife()

print( isinstance(swissKnife, SwissKnife)) # True
print( isinstance(swissKnife, Scissor)) # True
print( isinstance(swissKnife, (Scissor, Person))) # True, if any match

print(issubclass(SwissKnife, Role)) # False
print(issubclass(SwissKnife, (Role,Scissor))) # True, if any match

Composite Pattern 複合模式  

若單純的看功能面的話,繼承主要是讓子類別具有父類的能力,而子類卻不需增加額外的程式開發。
繼承之外 Composite Pattern(複合模式) 也具有相同能力,所以在這邊也介紹一下。
原則上便是類別槽串,外層類別具有內層類別功能。就像是細胞與包器間的關係。


class Screwdriver:
def __init__(self):
pass

def drill(self, message):
print('Screwdriver drilling: ', message)

class Knife:
def __init__(self):
pass

def cut(self, message):
print('Knife cutting: ', message)

class SwissKnife():
def __init__(self):
self.knife = Knife() # Composite
self.screwdriver = Screwdriver()

def cut(self, message): # Delegation
self.knife.cut(message)
print('SwissKnife cutting: ', message)

def drill(self, message):
self.screwdriver.drill(message)
print('SwissKnife drilling: ', message)

def cut_then_drill(self, message):
self.knife.cut(message)
self.screwdriver.drill(message)
print('SwissKnife drilling: ', message)