Try   HackMD

Class 類別(進階)

A language feature would not be worthy of the name “class” without supporting inheritance.
如果沒有支援繼承,「class」這個語言特色就不值得被稱為 class。
Python 官方說明書

Inheritance 繼承

class Car:
    def __init__(self, engine, price, speed):
        self.engine = engine
        self.price = price
        self.speed = speed
        
    def drive(self):
        print('GoGO')
        
    def accelerate(self):
        print('go ahead')
        
class AirPlane:
    def __init__(self, engine, price, height):
        self.engine = engine
        self.price = price
        self.height = height
        
    def drive(self):
        print('GoGO')
        
    def fly(self):
        print('up')
        
class Motorcycle:
    def __init__(self, engine, price):
        self.engine = engine
        self.price = price
        
    def drive(self):
        print('GoGO')
        
    def easter_egg(self):
        print('Never gonna give you up')

以上三個 Class 有大量重複的屬性跟方法,當程式碼達上百行或更多這樣的行為可能更多,違反物件導向程式設計中的 一次且僅一次

Once and only once, OAOO
又稱為 Don't repeat yourself, DRY
或 One rule, one place
但有時,為了可讀性,或避免耦合,或過早重構,應放棄DRY原則

class Transportation:
    def __init__(self, engine, price):
        self.engine = engine
        self.price = price
        
    def drive(self):
        print('GoGO')

class Car(Transportation):
    def __init__(self, engine, price, speed):
        super().__init__(engine, price)
        self.speed = speed
        
    def accelerate(self):
        print('go ahead')
        
class AirPlane(Transportation):
    def __init__(self, engine, price, height):
        super().__init__(engine, price)
        self.height = height
        
    def fly(self):
        print('up')
        
class Motorcycle(Transportation):
    def __init__(self, engine, price):
        super().__init__(engine, price)
        
    def easter_egg(self):
        print('Never gonna give you up')

這裡的 Transportation 稱為 Base Class (父類別)
Car, Airplane, Motorcycle 則是 Transportation 的 Sub Class (子類別)

Python3 可以直接用 super() 來呼叫父類別
Python2 則必須用 super(Class, self)
如範例程式就要改成 super(Transportation, self).__init__

Python 提供 isinstance(object_name, class_name) 來檢查一個物件與一個類別的關係

class Student:
    pass

class Teacher:
    pass

sun = Teacher()

print(isinstance(sun, Student)) # False
print(isinstance(sun, Teacher)) # True

isinstance 也能檢查到父類別
還有 issubclass(object_name, object_name) 提供了檢查類別間關係的方式

class Student:
    pass

class Teacher(Student):
    pass

sun = Teacher()

print(isinstance(sun, Student)) # True
print(isinstance(sun, Teacher)) # True

print(issubclass(Student, Teacher)) # False
print(issubclass(Teacher, Student)) # True
print(issubclass(Student, object)) # True 所有 class 都間接或直接繼承了 object
print(issubclass(Teacher, object)) # True

Method Overriding 方法覆寫

class Computer:
    def hi(self):
        print("I'm computer.")

class AMD(Computer):
    def hi(self):
        print("I'm AMD.")
        
A = AMD()
A.hi() # output: I'm AMD.

如果子類別有與父類別同名的方法,則子類別會覆寫該方法,稱為 Method Overriding
需要使用同名方法的話可以在子類別中以 super 呼叫

class AMD(Computer):
    def hi(self):
        super().hi()
        print("I'm AMD.")
        
A = AMD()
A.hi()
# output:
# I'm computer.
# I'm AMD.

Multi-Level Inheritance 多層繼承

class Animal:
    def __init__(self, height, weight):
        self.height = height
        self.weight = weight
        
    def hello(self):
        print(f"I'm {self.height} cm tall and weight {self.weight} kg.")
        
    def fly(self):
        pass

class Human(Animal):
    def __init__(self, height, weight, age):
        super().__init__(height, weight)
        self.age = age
    
    def hello(self):
        super().hello()
        print(f"I'm {self.age} years old.")

class Taiwanese(Human):
    def __init__(self, height, weight, age):
        super().__init__(height, weight, age)
        
class Student(Taiwanese):
    def __init__(self, height, weight, age, school):
        super().__init__(height, weight, age)
        self.school = school
    
    def hello(self):
        super().hello()
        print(f"I study at {self.school}.")

magical = Student(165, 50, 19, 'NTNU')
magical.hello()
# output:
# I'm 165 cm tall and weight 50 kg.
# I'm 19 years old.
# I study at NTNU.

多層繼承通常不建議超過兩層,否則程式碼較多時不易維護,且因較為複雜有時會繼承到不該繼承的東西(像是 class Human 繼承 class Animal 就莫名其妙會飛了)

Multiple Inheritance 多重繼承

class Phone:
    def __init__(self, cpu, size):
        self.cpu = cpu
        self.size = size
    
    def hi(self):
        print("I'm Phone.")

class Apple:
    def __init__(self, model):
        self.model = model
        
    def hi(self):
        print("I'm Apple.")

class iPhone(Phone, Apple):
    def __init__(self, model, cpu, size, gen):
        Phone.__init__(self, cpu, size)
        Apple.__init__(self, model)
        self.gen = gen

    def hello(self):
        print(f"I'm gen {self.gen} {self.model}." )

myphone = iPhone('iPhone mini', 'A15', 5.4, 13)
myphone.hi()
myphone.hello()
# output:
# I'm Phone.
# I'm gen 13 iPhone mini.

多重繼承是一種類別同時有兩個或以上父類別
要特別注意內部如果有同名屬性或方法會依繼承順序以遞迴方式繼承
(範例中是先 PhoneApple ,因此調用 hi() 時的輸出為 PhoneI'm Phone
包含魔術方法也是,因此使用多重繼承時的初始化方式也不一樣

除了魔術方法在命名上必須按照標準,在使用多重繼承時應盡可能避免同名方法,免得換別人維護那份程式碼的時候因為交換繼承的順序而產生不如預期的結果

在某些情形下依然能使用 super() 來調用多個父類別的方法,有空的可以試試看
Just trial and error.