# 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,所以沒有辦法存取類別或物件屬性。