Try   HackMD

Python OOP物件導向程式設計

類別與物件

tags: python

類別(Class)

類別就像一個設計圖或是輪廓。

例如要生產一輛汽車時,都會有設計圖,生產時依照這個決定汽車長什麼樣子以及有什麼功能。

類別(Class)就類似設計圖,用來定義產生物件(Object)後,該物件會有什麼屬性(Attribute)和方法(Method)。

定義類別

首先會有class關鍵字,接著自定類別名稱,最後加上冒號。類別名稱的命名原則習慣上單字第一個字母大寫,每個相連的不同的單字的第一個字母也都為大寫。

# 定義Car類別
class Car:
    pass

物件(Object)

透過類別(Class)建立的實體稱為物件(Object),就像實際生產出來的汽車(例如:Benz、BMW)。

myCar = Car()  # 透過Car類別建立myCar實體

屬性(Attribute)

用來存放物件的資料。

建立物件的屬性語法如下:

object_name.attribute_name = value

建立屬性範例:

myCar = Cars()   # 建立Cars類別的物件 
myCar.color = "white"  #顏色屬性 
maCar.gas = 100  # 設定油量

存取物件的屬性值則透過以下語法:

object_name.attribute_name 

存取範例:

print(myCar.color)  # 執行結果:white
print(myCar.gas)    # 執行結果:100

方法(Method)

為物件提供的功能。

定義方法和定義函式一樣都是def關鍵字開頭,接著自訂名稱,通常第一個單子的首字母為小寫,且方法必須有self參數(第一個參數),語法如下:

def method_name(self, param...):  
    statement 

範例:

class Car:
    def fillGas(self, gas):
        self.gas = gas
        
myCar = Car()
myCar.fillGas(200)

print(myCar.gas)

在類別內所有的方法第一個參數都是self,我們並不需要自己傳入self參數,直譯器會自動幫我們帶入,只需要從第二個參數開始傳入即可。

建構式(Constructor)

定義__init__()方法

__init()方法可以將該類別的初始化流程都定義在裡面,該方法在建立類別物件的時候會自動被呼叫,所以不需要自行呼叫。

class Car:
    def __init__(self, color, gas):
       self.color = color
       self.gas = gas
       
    def show(self):
        print(self.color)
        print(self.gas)
        
myCar = Car('white', 100)
myCar.show()

請注意__init__()方法前後都是兩個底線。

__dict__

每個類別都會有這個屬性,為一個字典(dict),可以用來取得該物建內所有的數性名稱和屬性值。

特殊物件方法

__str__

透過定義__str__()方法,可以在該物件透過呼叫str()函式時輸出自訂的字串,例如:

class Car:
    ... 程式碼同上個範例,省略 ...
    
    def __str__(self):
       return 'Color=' + self.color + ', gas=' + str(self.gas)
        
myCar = Car("white", 100)
print(str(myCar))

請注意,該方法一定要回傳一個字串。

__add__

說明

定義加法運算

格式
def __add__(self, <要做運算的另一個物件>):
    return <計算結果>

__sub__

說明

定義減法運算

格式
def __sub__(self, <要做運算的另一個物件>):
    return <計算結果>

__mu__

說明

定義乘法運算

格式
def __mul__(self, <要做運算的另一個物件>):
    return <計算結果>

__truediv__

說明

定義除法運算

格式
def __truediv__(self, <要做運算的另一個物件>):
    return <計算結果>

__eq__

說明

定義「==」運算

格式
def __eq__(self, <要比較的物件>):
    return <比較結果>

繼承

有些類別會有一些相關的程式碼,例如:

class Bus:
  def __init__(self, color, gas):
    self.color = color
    self.gas = gas
  
  def run(self):
    print('巴士開動')

  def showStatus(self):
    print(f'車子顏色為:{self.color}色')
    print(f'車子汽油量:{self.gas}公升')

class Truck:
  def __init__(self, color, gas):
    self.color = color
    self.gas = gas
  
  def run(self):
    print('卡車開動')
    
  def showStatus(self):
    print(f'車子顏色為:{self.color}色')
    print(f'車子汽油量:{self.gas}公升')

bus = Bus('白', 100)
truck = Truck('藍', 200)

bus.run()
bus.showStatus()
truck.run()
truck.showStatus()

其建構式和showStatus()方法其實程式碼都是一樣的,因此我們可以將之提取到一個類別Car:

class Car:
  def __init__(self, color, gas):
    self.color = color
    self.gas = gas

  def showStatus(self):
    print(f'車子顏色為:{self.color}色')
    print(f'車子汽油量:{self.gas}公升')

然後讓Bus和Truck類別都繼承自Car類別:

class Bus(Car):
  def run(self):
    print('巴士開動')

class Truck(Car):
  def run(self):
    print('卡車開動')

繼承的方式也就是在類別名稱定義後面多一對小括號,並且將要繼承的類別名稱放在裡面,該Car類別也稱之為父類別;因為Bus和Truck都繼承自Car類別,因此Car類別內的方法,在Bus和Truck類別內都可以呼叫使用,Bus和Truck類別也稱之為子類別,例如:

bus.run()
bus.showStatus()
truck.run()
truck.showStatus()

這樣一來,如果未來showStatus()方法內的邏輯需要改變,只要修改服類別即可,而不用每個類別的方法都要修一次。

方法覆載

當子類別內的方法和父類別相同時,子類別的方法將會覆蓋掉父類別的方法,例如:

class ParentClass:
  def show(self):
    print('我是父類別')

class ChildClass(ParentClass):
  def show(self):
    print('我是子類別')

c = ChildClass()
c.show()

如果在子類別內想呼叫父類別的show()方法,可以使用super()方法,例如:

super().show()

就可以呼叫父類別的show()方法。

其他相關

判斷類別與物件的關係:isinstance()

Python提供了一個函式isinstance()來判斷類別(Class)與物件(Object)的關係,語法如下:

isinstance(object_name, class_name)

範例:

# 汽車類別
class Car:
    pass

# 機車類別
class Motorcycle:
    pass

# 建立Cars類別的物件
myCar = Car()

print(isinstance(myCar, Car))         # 執行結果:True
print(isinstance(myCar, Motorcycle))  # 執行結果:False

由於myCar並不是Motorcycle類別產生的物件實體,所以執行結果為False。

進階

實體方法(Instance Method)

最基本的類別中的方法,其條件為:

  1. 該方法沒有加任何裝飾詞(Decorator)。
  2. 至少有一個self參數,該方法被呼叫時指向物件(Object)。

範例:

class Car:
    def fill(self, gas):
        print("fill是個實體方法.")

在沒有實現__str__方法下,如果直接print(self),可以看到,self變數內存放的就是物件,例如:

<__main__.Car object at 0x112addee0>

類別方法(Class Method)

特色為:

  1. Python類別(Class)中有@classmethod裝飾詞(Decorator)的方法(Method)。
  2. 被呼叫時,其方法第一個參數為class參數,指向類別(Class)。
class Car:
    # 類別方法(Class Method)
    @classmethod
    def drive(cls):
        print("drive是一個類別方法.")

由於類別方法(Class Method)的cls參數指向類別(Class),所以類別方法(Class Method)只能修改類別的屬性,而無法改變物件的屬性,因為它沒指向物件的self參數。

class Car:
    maxSpeed = 4  # 類別屬性
    
    # 類別方法(Class Method)
    @classmethod
    def drive(cls):
        print(f"{cls} max speed is {cls.maxSpeed}.")
        
myCar = Car()
myCar.drive()  #透過物件呼叫
Car.drive()    #透過類別呼叫

執行結果:

<class '__main__.Car'> max speed is 4.
<class '__main__.Car'> max speed is 4.
類別方法(Class Method)在Python裡常應用於產生物件,例如:
class Car:
    # 建構式
    def __init__(self, maxGas, seat):
        self.maxGas = maxGas
        self.seat = seat
        
    # 跑車
    @classmethod
    def sportCar(cls):
        return cls(100, 2)
        
    # 廂型車
    @classmethod
    def vans(cls):
        return cls(300, 6)
        

myFirstCar = Car.sportCar()
mySecondCar = Car.vans()

你也可以用 myFirstCar = Car(100, 2)來產生一輛跑車實體物件,但當你需要在很多地方都產生跑車實體時,用類別方法來產生實體的做法會比較簡潔且不容易出錯。

靜態方法(Static Method)

特色為:

  1. 使用@staticmethod裝飾詞(Decorator)的方法(Method)。
  2. 沒有self及cls參數。
    範例:
class Car:
    #靜態方法
    @staticmethod
    def show():
        print("這是一個靜態方法.") 

靜態方法在類別中是一個獨立的方法,通常應用於方法中不需要存取物件及類別的的屬性或方法的情境,它可以透過物件或是類別來呼叫。

透過物件呼叫
myCar = Car()
myCar.show()
透過類別呼叫
Car.show()

執行結果

  1. 不論透過類別或物件都可以呼叫,執行期間不會傳入self及cls參數。
  2. 一般會用在單元的測試。
  3. 因為參數不會有self或cls,所以沒有辦法存取類別或物件屬性。