# Python Deep Dive IV 第 6 節 Single Inheritance

**URL:** https://vip.studycamp.tw/t/python-deep-dive-iv-%E7%AC%AC-6-%E7%AF%80-single-inheritance/6025
**Category:** 4. OOP
**Tags:** 上課筆記
**Created:** [2023年五月3日 07:04 UTC](https://vip.studycamp.tw/t/python-deep-dive-iv-%E7%AC%AC-6-%E7%AF%80-single-inheritance/6025 "2023-05-03T07:04:44Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![ChrisWei](https://vip.studycamp.tw/user_avatar/vip.studycamp.tw/chriswei/32/196_2.png) [@ChrisWei](https://vip.studycamp.tw/u/ChrisWei)
#### Post date: [2023年五月3日 07:04 UTC](https://vip.studycamp.tw/t/python-deep-dive-iv-%E7%AC%AC-6-%E7%AF%80-single-inheritance/6025/1 "2023-05-03T07:04:44Z")

</div>

- 這一節是講 OOP Inheritance 的序章，Python 支持一個以上的類別繼承，因此在類別物件的繼承上，可以是單一繼承或是多重繼承。而首先第六節要聚焦在單一繼承的概念上，之後的章節則會接著講述多重繼承的概念。

- 在進入課程之前，首先來快速導覽一下 Python 的 Inheritance 概念，以下的前導文章(也包含下圖)均取自 [**Types of inheritance Python – GeeksForGeeks**](https://www.geeksforgeeks.org/types-of-inheritance-python/)  

## Inheritance 繼承:

Python 的繼承主要可分為五種方式:

1. Single Inheritance 單一繼承: 衍生的子類別繼承自單一的父類別，例如:

```python
# Python program to demonstrate
# single inheritance

# Base class
class Parent:
    def func1(self):
        print("This function is in parent class.")

# Derived class
class Child(Parent):
    def func2(self):
        print("This function is in child class.")

# Driver's code
object = Child()
object.func1()
object.func2()

```

1. Multiple Inheritance 多重繼承: 衍生的子類別(Child Class)，繼承自多重的父類別(Parents Class) ，例如:

```python
# Python program to demonstrate
# multiple inheritance

# Base class1
class Mother:
    mothername = ""
    def mother(self):
        print(self.mothername)

# Base class2
class Father:
    fathername = ""
    def father(self):
        print(self.fathername)

# Derived class
class Son(Mother, Father):
    def parents(self):
        print("Father :", self.fathername)
        print("Mother :", self.mothername)

# Driver's code
s1 = Son()
s1.fathername = "RAM"
s1.mothername = "SITA"
s1.parents()

```

1. Multilevel Inheritance 縱向式(多層式)繼承: 一個子類別繼承自父類別後，這個子類別又再作為父類別，使其他的子類別(孫)繼承該子類別，而形成階層式的繼承關係。

2. Hierarchical Inheritance 橫向式繼承: 一個父類別，同時由多個子類別繼承的橫向繼承關係。

3. Hybrid Inheritance 混合式繼承: 綜合以上幾種的混合繼承架構。  
(礙於篇幅的關係，就不貼範例了，其實剛剛擷取的範例，也都取自 [**Types of inheritance Python --GeeksForGeeks**](https://www.geeksforgeeks.org/types-of-inheritance-python/) 這篇中的範例)

# Single Inheritance:

## 什麼是繼承?

老師首先以形狀來對比類別的繼承概念，比方說形狀基本上可以區分為多邊形(Polygon)、線條(Line)以及橢圓形(Ellipse)，而其中圓形(Circle)是橢圓形的特例，而四邊形(Quadrilateral)是多邊形的一個特例，而矩形(Rectangle)又是四邊形的特例，再以下正方形(Square)又是矩形的特例，這樣從正方形一路到矩形、四邊形、多邊形，形成了一個特殊的繼承鏈。

同理，三角形(Triangle)也是多邊形的一個特例，而等腰三角形(Isosceles)則是三角形的一個特例，再往下正三角形(Equilateral)又是等腰三角形的特例，如下圖，諸如此類，這樣的彼此間的從屬關係，最終形成一個 IS-A Relationships，這也是 OOP 中用來表示 a 包含 b 的包含關聯圖(subsumption relationship，細節可參考 [維基百科的說明](https://en.wikipedia.org/wiki/Is-a) )。

IS-A Relationships 能夠充分表達抽象類別之間的超類(Super; Parents)與子類(Child)的關係，而透過繼承，子類會因繼承(Inherit)而擁有超類的一些屬性與方法，而子類除了超類的屬性與方法外，能進一步延伸(Extend)產生自己特有的屬性與方法，例如多邊形的邊長、內角等等，更有甚者，能夠複寫(Override)超類的方法或屬性，例如三角形的邊長~

 ![Image4](https://vip.studycamp.tw/uploads/default/original/2X/b/b110584325fdf145f90537ff929de825a87afe41.png)  
(上圖擷取自 Udemy [**Dr. Fred Baptiste**](https://www.udemy.com/user/fredbaptiste/) Python Deep Dive IV 課程內容)

## Single Inheritance 單一繼承

Single Inheritance 單一繼承: 衍生的子類別繼承自單一的父類別，這也是上與下最單純的繼承關係:

- 父類別中的屬性或方法，可以在子類別的 Instance 產生後就自然能使用這些屬性或方法~

- 子類別可以透過複寫(Override)來改變原先從繼承的父類別過來的屬性或函式

- 子類別中可以自行創建自己的屬性或方法(非繼承自父類別的屬性或方法)，稱為延伸(Extend)

- 子類別的 Instance，自然由於子類別的繼承關係，也會是父類別的 Instance

- 即便是最底層的父類別，也仍存在繼承關係，所有的類別都繼承自 Python object class，因此所有的類別也都不需經由複寫 Dunder Method，而能擁有一些 Dunder Method 的屬性，例如 ` __name__ `, ` __class__ `, ` __init__ `, etc.

- 在繼承鏈上的任何父類別中的方法，我們都可以在子類別中進行複寫，甚至包含最底層的 object class

 ![Image5](https://vip.studycamp.tw/uploads/default/original/2X/7/7a4bc205e93d68e5ce4cec8310c7ee0c575d6be1.jpeg)  
(上圖擷取自 Udemy [**Dr. Fred Baptiste**](https://www.udemy.com/user/fredbaptiste/) Python Deep Dive IV 課程內容)

## 如何驗證兩個類別彼此存在繼承關係?

- issubclass(child-c, parent-c): 只能用於驗證兩類別的從屬繼承關係，第一個參數必須是子類別，第二個參數則是父類別，此方法不直接適用於 Instance 的驗證，如要用於 Instance 驗證 → issubclass(type(obj), parent-c)

- isinstance(obj, class): 此方法是我們最常使用的，其中 obj 參數是 instance，class 參數則是要驗證是否有從屬關係的目標類別，由於從屬關係指的是整個繼承鏈，因此這個 class 可以是直接 instance 的來源子類別，也可以是這個子類別於繼承鏈上的所有父類別~

- 如果你想用 isinstance() 來驗證的兩個類別間的繼承關係，可以先將其中一個類別進行 Instance 創建，再由創建的 Instance 來使用 isinstance() 驗證與目標類別有無從屬關係

- 在條件式中使用 type() == instance-class 與 isinstance()，**老師比較推薦使用 isinstance() 來作為條件**，因為 type() 無法驗證這個子類物件與超類的從屬關係，特別是如果 Instance 是來自於一個繼承的子類別時，會很容易造成條件誤判~

## 範例演示 for Single Inheritance

```python
class Shape:
    pass

class Ellipse(Shape):
    pass

class Circle(Ellipse):
    pass

class Polygon(Shape):
    pass

class Rectangle(Polygon):
    pass

class Square(Rectangle):
    pass

class Triangle(Polygon):
    pass

issubclass(Ellipse, Shape) # True
issubclass(Square, Shape) # True
e = Ellipse()
isinstance(e, Ellipse) # True

class Shape:
    def __init__ (self, name):
        self.name = name
        
    def info(self):
         return f'Shape.info called for Shape({self.name})'
    
    def extended_info(self):
        return f'Shape.extended_info called for Shape({self.name})'
    
class Polygon(Shape):
    def __init__ (self, name):
        self.name = name # we'll come back to this later in the context of using the super()
        
    def info(self):
        return f'Polygon info called for Polygon({self.name})'

p = Polygon('square')
p.info() # 'Polygon info called for Polygon(square)'
p.extended_info() # 'Shape.extended_info called for Shape(square)'

class Shape:
    def __init__ (self, name):
        self.name = name
        
    def info(self):
         return f'Shape.info called for Shape({self.name})'
    
    def extended_info(self):
        return f'Shape.extended_info called for Shape({self.name})', self.info()
    
class Polygon(Shape):
    def __init__ (self, name):
        self.name = name # we'll come back to this later in the context of using the super()
        
    def info(self):
        return f'Polygon.info called for Polygon({self.name})'

p = Polygon('Square')
p.info() # 'Polygon.info called for Polygon(Square)'
print(p.extended_info()) # ('Shape.extended_info called fo，bjr Shape(Square)', 'Polygon.info called for Polygon(Square)')

```

## 單一繼承中的子類別的行為:

### 1. Overriding 複寫: 父類別中定義的屬性或方法，子類別可以在其類別中重新宣告及定義 → Overriding 複寫。

- 如上面範例 Polygon 子類別繼承了 Shape 父類別，但在 Polygon 子類別中重新宣告了 info method，而此 info method 在 Shape 父類別中有定義過，因此以 info method 來說，Polygon 子類別複寫了 Shape 父類別的 info method。

### 2. Extending 延伸: 子類別繼承父類別後，可於子類別中定義父類別沒有的新的屬性或方法 → Extending 延伸。

- 例如下面範例所示，Student 類別繼承了 Person 父類別，而 Student 類別中新定義了一個 study method，此 study method 是 父類別 Person 中所沒定義過的方法:

```auto
class Person:
    pass

class Student(Person):
    def study(self):
        return 'study... study... study...'

p = Person()
try:
    p.study()
except AttributeError as ex:
    print(ex)

```

### Delegating to Parent (從父類別中)委派: 當子類別繼承父類別後，子類別不須宣告(如果宣告了，就變成是複寫) 就能呼叫父類別的屬性或方法來直接使用 → Delegating to Parent。

- 當要進行對父類別屬性或方法的呼叫時，記得要使用關鍵字 super()(特別注意: 不是使用 self，self 指的是子類別自己)
- 即便是委派(從父類別的方法或屬性)的行為，如果一個屬性或方法，在子類別與父類別同時存在，子類別的 Instance 會優先呼叫子類別上的同名屬性或方法，此為一種遮蔽效應，因此當一個單一且多重繼承的子類別，則會椅子類別為首，當進行委派呼叫時，會逐層從子類別找，再接續到父類別，而後才是祖父，曾祖父，曾曾祖父的逐層往上找，往上進行委派呼叫，如以下範例(截圖取自 [Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)

 ![Image2](https://vip.studycamp.tw/uploads/default/original/2X/4/43945c94a5347cc2538fb74efcb414ee034a7c07.jpeg)

- 由於委派時，會再呼叫子類別以外的父類別的方法或屬性，因此使用 super().xxx() 呼叫父類別的方法與屬性時，要特別注意程式碼的順序性，避免因為委派，讓子類別的屬性值被誤寫回父類別的屬性值，如以下截圖(from [Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)所示:

 ![Image](https://vip.studycamp.tw/uploads/default/original/2X/2/2ce10c807b5f45ca41b58e6e70ee5194b1462dbb.png)

## Slots 與 Single Inheritance:

### 1. Slots:

- 我們知道 Instance 的 Attribute 都存放到本地端的 **dict** 集合中，因此對於 Instance 的產生會對本地端的 **dict** (存放在執行環境當下的 memory 中) 造成一些 overhead，因此當產生 Instance 的類別中的屬性或方法越多，以及 Instance 產生的越多時，對本地端環境的 memory 的 overhead 也就越大。
- 因此從 Python 3.3 起，Python 引入了 key sharing dictionaries 來緩解這個問題，但有另一個方式更好，就是使用 slots → **slots** ， **slots** 類似 **dicts** 也是一個寄存物件的字典物件!

```auto
class Location:
    __slots__ = 'name', '_longitude', '_latitude'
    
    def __init__ (self, name, longitude, latitude):
        self._longitude = longitude
        self._latitude = latitude
        self.name = name
        
    @property
    def longitude(self):
        return self._longitude
    
    @property
    def latitude(self):
        return self._latitude

```

- 我們可以在 Class 定義時，先使用 **slots** 來存放這些屬性，如下圖示，而當 Python Interpreter 在解析到這個類別時，就會知道這些屬性已被存放到 slots 中，而不會額外 Create **dict** 來存放這些已被存到 **slot** 中的屬性或方法
- 我們可以在 Class 定義時，先使用 **slots** 來存放這些屬性，如下圖示，而當 Python Interpreter 在解析到這個類別時，就會知道這些屬性已被存放到 slots 中，而不會額外 Create **dict** 來存放這些已被存到 **slot** 中的屬性或方法
  - 真有適用於 slots 的場合嗎? 何時會大量產生 Instance，其實像從資料庫返回多筆(可能成百上千筆的資料筆數) 資料列，每個資料列舊都可看成是一個 Instance，像這樣的場景就很適用使用 slots 來節省記憶體。(以下截圖取自[Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)

 ![Image3](https://vip.studycamp.tw/uploads/default/original/2X/7/724d0027fe478ae8844a6751d9b507563a32a8ab.png)

```
* 那節省了記憶體，會否會影響到效能? --> 結論是使用 slots 甚至比一般字典還快~ (以下截圖取自[Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)

```

 ![Image4](https://vip.studycamp.tw/uploads/default/original/2X/e/e71c83ab4535da564d269b34849e448664e7576c.png)

```
* 既然 slots 有這麼多優點，為何不設成全時(All the time)都使用 slots?
    * 這是因為 slots 有一個不便性，當我們在一個 Class 上使用了 slots，那麼這個 Class create 的 Instance 都不能增加任何原先沒在 slots 中定義的屬性，如下圖所示~ (以下截圖取自[Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)
        * 一旦使用了 slots， __dict__ 就會被移除，因此我們不再能自由地增加屬性到 Instance 上。

```

 ![Image5](https://vip.studycamp.tw/uploads/default/original/2X/2/28e95e117d4994524f8cdc2b5eb57b3e7c73325c.png)

### 2. Slots 與 Singel Inheritance 間的交互影響:

- 讓我們來看看 Single Inheritance 與 slots 的交互影響，如下圖所示: (以下截圖取自[Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)

 ![Image6](https://vip.studycamp.tw/uploads/default/original/2X/6/62f7a0d755b5f92488c11684dcf95b75d1d807ae.jpeg)

```
* 那如果子類別也想要把 extension attribute 也寄存到 slots 裡，該如何做?
    * 子類別可以自己也在類別中指定 __slots__ 來存放自己的屬性!!! (以下截圖取自[Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)

```

 ![Image7](https://vip.studycamp.tw/uploads/default/original/2X/2/2d930b227e23c509b966da06c9cb2ad79ce3d7e9.png)

```
* 要注意一點，當子類別也指派了新的屬性存放到 slots 中，但新屬性名稱又與父類別存放到 slots 的屬性名稱一樣時，會發生什麼，以目前而言，仍能正常運作，但會增加記憶體的消耗，但老師也提到，未來的話，在 Python 官方文件有提到，未來會加入檢查機制來防止這樣的狀況發生(說不定就直接 raise an exception error)
* 當剛剛那樣的情形(當子類別也指派了新的屬性存放到 slots 中，但新屬性名稱又與父類別存放到 slots 的屬性名稱一樣時)發生時會如何?
    * 除了子類別同名屬性會遮蔽父類別同名屬性外，如果原來父類別同名屬性有設定檢查機制(但子類別上沒設定)，那這些設定也會無效(直接 follow 子類別屬性的定義，如父類別有定義，子類別上沒定義，則就會被蓋掉而變成沒定義)，如以下截圖所示(from [Dr. Fred Baptiste](https://www.udemy.com/user/fredbaptiste/) 的 [Python Deep Dive VI](https://www.udemy.com/course/python-3-deep-dive-part-4) 課程簡報內容)

```

 ![Image8](https://vip.studycamp.tw/uploads/default/original/2X/d/d187dbe7a519d6768040aee58df7f83761d330e7.png)

```
* 另一種情形是當父類別沒有使用 slots，但子類別有使用 slots 時，會如何?
    * 子類別仍能正常使用 slots 不會報錯，但是子類別衍生的 Instance，這些寄存到 slots 中的屬性，仍會出現在本地端的 __dict__ 裡~

```

```auto
class Person:
    def __init__ (self, name):
        self.name = name
        
class Student(Person):
    __slots__ = 'age', 
    
    def __init__ (self, name, age):
        super(). __init__ (name)
        self.age = age

s = Student('Python', 30)
s.name, s.age, s. __dict__ # ('Python', 30, {'name': 'Python'})

```

```
* 接下來來探討被寄存到 slots 裡的屬性(slotted attributes) 與原先的屬性(Properties)(像 getter、setter 這些屬性)有沒有不一樣，如有不同，不同點是什麼?
    * 最直接的不同，一個已被寄存到 slots 中的屬性，並不會再被儲存到 Instance 的 __dict__ 中
    * 像 getter、setter 這些 @properties 屬性設置，本來也都沒存在 Instance 的 __dict__ 中
    * 本質上，這兩者 (slots & @properties) 並無不同，都是出自 data descriptors 這個類別
    * slots 中的屬性執行比一般屬性(Attributes)快且更少的儲存需求(前面已經提過了)
    * 但 Instance dictionary 則賦於屬性(Attributes)的指派與設定有更多的自由度
    * 那我們兩者可同時使用嗎? 
        * 答案是肯定的，我們可以把 __dict__ 直接指派寄存到 __slots__ 中:

```

```auto
class Person:
    __slots__ = 'name', ' __dict__'
    
    def __init__ (self, name, age):
        self.name = name
        self.age = age

p = Person('Alex', 19)
p.name, p.age, p. __dict__ # ('Alex', 19, {'age': 19})
p.school = 'Berkeley'
p. __dict__ # {'age': 19, 'school': 'Berkeley'}

```

~ 以上大致是第六節 Single Inheritance 的重點摘要 ~
