# n.Python - 類別與物件導向程式設計(Object_Oriented_Programming)
###### tags: `Python`
## 1.物件變數與__init__特殊 method
* <font color="#0080FF">**可隨時建立的物件變數 (不好的方式)**</font>
```python=+
class Circle:
'''Circle : An Empty Class'''
pass
c1 = Circle()
c1.radius = 3 #不存在的變數賦值 = 建立變數
print(2 * 3.14 * c1.radius) #計算圓的周長
c2 = Circle()
c2.r = 5
print(2 * 3.14 * c2.r) #計算圓的周長
print(Circle().__doc__)
```
> ```18.84```</br>
> ```31.400000000000002```</br>
> ```Circle : An Empty Class```
##
* <font color="#0080FF">**初始化物件變數**</font>
```python=+
class Circle:
def __init__(self): # <- c1被傳進這裡的self
self.radius = 1
c1 = Circle() #(important!)c1物件會自動被傳入到上面的self
print(2 * 3.14 * c1.radius)
c1.radius = 5 #修改radius變數值
print(2 * 3.14 * c1.radius)
```
> ```6.28```</br>
> ```31.400000000000002```
## 2.物件的函式 (method)
* <font color="#0080FF">**定義及呼叫方法**</font>
```python=+
class Circle:
def __init__(self):
self.radius = 1
def area(self):
return self.radius * self.radius * 3.14159
c1 = Circle()
print(c1.area())
c1.radius = 3
print(c1.area())
print(Circle.area(c1)) #(important!)另外一種呼叫 c1 物件 area 的方法
```
> ```3.14159```</br>
> ```28.27431```</br>
> ```28.27431```
##
* <font color="#0080FF">**<!!>物件變數、區域變數**</font>
```python=+
class Circle:
def __init__(self):
self.radius = 1
radius = 2 #__init__裡的區域變數
#print(radius) 在此處執行print -> radius = 2
c1 = Circle()
print(c1.radius) #print出物件變數
```
> ```1```
##
* <font color="#0080FF">**建立物件時傳遞引數、預設值**</font>
```python=+
class Circle:
def __init__(self,r = 1):#未給予引數的情況下使用預設值
self.radius = r
def area(self):
return self.radius * self.radius * 3.14159
c1 = Circle()
print(c1.area())
c2 = Circle(3)
print(c2.area())
```
> ```3.14159```</br>
> ```28.27431```
## 3.類別變數 (class variables)
> <font color="#EA0000">**「類別變數」可以作為「物件變數」的預設值。**</font>
> <font color="#EA0000">**優點:可節省每次建立物件時初始化該物件變數的時間和記憶體的成本</br>缺點:容易搞不清楚目前到底是參照了物件變數或類別變數**</font>
* <font color="#0080FF">**建立物件時建立類別變數、傳遞引數、預設值**</font>
```python=+
class Circle:
pi = 3.14159 #建立類別變數 pi (不是在__init__中建立)
def __init__(self,r = 1):
self.radius = r
def area(self):
return self.radius * self.radius * Circle.pi #使用類別變數 pi
c1 = Circle()
print(c1.area())
c2 = Circle(3)
print(c2.area())
```
> ```3.14159```</br>
> ```28.27431```
##
* <font color="#0080FF">**<!>物件的特殊屬性**</font>
```python=+
Circle
c1.__class__
c1.__class__.pi
```
> ```__main__.Circle #兩者指向同一個類別```</br>
> ```__main__.Circle #兩者指向同一個類別```</br>
> ```3.14```
##
* <font color="#0080FF">**<!!>存取類別變數 (利用物件的特殊屬性)**</font>
```python=+
#避免日後更改類別名稱的方法
class Circ:
pi = 3.14159
def __init__(self,r = 1):
self.radius = r
def area(self):
return self.radius * self.radius * self.__class__.pi #不寫死類別名稱,就不用擔心日後更改了!
c1 = Circ()
print(c1.area())
c2 = Circ(3)
print(c2.area())
```
> ```3.14159```</br>
> ```28.27431```
##
* <font color="#0080FF">**<!!>類別變數、物件變數**</font>
> <font color="#EA0000">**請記得 Python 遇到賦值敘述時,若變數不存在,就會自動建立該變數!!**</font>
```python=+
c1 = Circle(1)
c2 = Circle(2)
c1.pi = 3.14 #(important!)此處為建立物件變數,而不是更改類別變數
c1.pi
#若找不到物件變數時,會自動尋找同名的類別變數
c2.pi
```
> ```3.14```</br>
> ```3.14159```
## 4.<!!>靜態方法 (Static method) 與 類別方法 (Class method)
<font color="#0080FF">**circle.py**</font>
```python=+
'''circle 模組 : 包含 Circle 類別'''
class Circle:
'''Circle 類別'''
all_circles = [] #類別變數
pi = 3.14159 #類別變數
def __init__(self,r = 1):
'''以給予的半徑建立圓物件'''
self.radius = r
self.__class__.all_circles.append(self)
def area(self):
'''計算此圓形的面積'''
return self.__class__.pi * self.radius * self.radius
@staticmethod
def total_area(): #(important!)靜態方法不會傳遞物件本身作為第一個參數,因此不需要"self"
'''用來計算 all_circles 這個 list 所有物件總面積的靜態方法'''
total = 0
for c in Circle.all_circles:
total = total + c.area()
return total
```
##
* <font color="#0080FF">**呼叫靜態方法**</font>
```python=+
import circle
c1 = circle.Circle(1)
c2 = circle.Circle(2)
circle.Circle.total_area()
c2.radius = 3 #把物件變數 c2.radius 改為 3
circle.Circle.total_area()
c1.total_area() #從物件也可以呼叫到類別的靜態方法
```
> ```15.70795```</br>
> ```31.415899999999997```</br>
> ```31.415899999999997```
##
<font color="#0080FF">**circle_cm.py**</font>
```python=+
'''circle_cm 模組 : 包含 Circle 類別'''
class Circle:
'''Circle 類別'''
all_circles = [] #類別變數
pi = 3.14159 #類別變數
def __init__(self,r = 1):
'''以給予的半徑建立圓物件'''
self.radius = r
self.__class__.all_circles.append(self)
def area(self):
'''計算此圓形的面積'''
return self.__class__.pi * self.radius * self.radius
@classmethod #類別方法class method
def total_area(cls):
#(important)類別方法會以本身作為第一個參數傳遞,而代表類別本身的參數命名慣例是"cls"
'''用來計算 all_circles 這個 list 所有物件總面積的類別方法'''
total = 0
for c in cls.all_circles:
#(important!)靜態方法是使用固定的類別名稱,類別方法是將類別本身傳遞為參數,不用擔心日後改名!!
total = total + c.area()
return total
```
##
* <font color="#0080FF">**呼叫類別方法 (不用擔心日後類別改名)**</font>
```python=+
import circle_cm
c1 = circle_cm.Circle(1)
c2 = circle_cm.Circle(2)
circle_cm.Circle.total_area()
c2.radius = 3
circle_cm.Circle.total_area()
```
> ```15.70795```</br>
> ```31.415899999999997```
## 5.類別的繼承 (inheritance)
* <font color="#0080FF">**未繼承的情況,直接增加新的類別**</font>
```python=+
#同樣都有屬性x,y
class Square:
def __init__(self,s = 1,x = 0,y = 0):
self.side = s
self.x = x
self.y = y
class Circle:
def __init__(self,r = 1,x = 0,y = 0):
self.radius = r
self.x = x
self.y = y
```
##
* <font color="#0080FF">**將相似類別繼承到相同的父類別**</font>
```python=+
class Shape:
def __init__(self,x,y):
#將相同屬性x,y寫成父類別
self.x = x
self.y = y
class Square(Shape): #繼承(Shape)
def __init__(self,s = 1,x = 0,y = 0):
#(important!)
#系統預設 -> 子類別建構第一句一定是「super()」
super().__init__(x,y) #繼承時必須先呼叫自己父類別的__init__方法
'''
Shape.__init__(self,x,y) 也可以,但寫死名稱不推薦
若現在不用Shape當父類別,則內部code需要更改多次
'''
self.side = s #再進行屬於自己的初始化動作
class Circle(Shape): #繼承(Shape)
def __init__(self,r = 1,x = 0,y = 0):
#(important!)
#系統預設 -> 子類別建構第一句一定是「super()」
super().__init__(x,y) #繼承時必須先呼叫自己父類別的__init__方法
self.radius = r #再進行屬於自己的初始化動作
```
##
* <font color="#0080FF">**將相似類別繼承到相同的父類別(續)**</font>
```python=+
class Shape:
def __init__(self,x,y):
#將相同屬性x,y寫成父類別
self.x = x
self.y = y
#新增一個共同繼承的方法
def move(self,delta_x,delta_y):
self.x = self.x + delta_x
self.y = self.y + delta_y
class Square(Shape):
def __init__(self,s = 1,x = 0,y = 0):
super().__init__(x,y) #繼承時必須先呼叫自己父類別的__init__方法
self.side = s #再進行屬於自己的初始化動作
class Circle(Shape):
def __init__(self,r = 1,x = 0,y = 0):
super().__init__(x,y) #繼承時必須先呼叫自己父類別的__init__方法
self.radius = r #再進行屬於自己的初始化動作
```
##
* <font color="#0080FF">**呼叫父類別方法**</font>
```python=+
c1 = Circle(5)#建立物件
print(c1.x,c1.y,c1.radius)
c1.move(3,4)
print(c1.x,c1.y,c1.radius)
```
> ```0 0 5```</br>
> ```3 4 5```
## 6.類別變數與物件變數的繼承
* <font color="#0080FF">**兩個不同的類別定義的繼承關係**</font>
```python=+
class P:
z = 'Hello'
def set_p(self):
self.x = 'Class P'
def print_p(self):
print(self.x)
class C(P):
def set_c(self):
self.x = 'Class C'
def print_c(self):
print(self.x)
```
##
* <font color="#0080FF">**參照相同的物件變數名稱**</font>
```python=+
c1 = C()
c1.set_p()
c1.print_p() #Class P
c1.print_c() #Class P
c1.set_c()
c1.print_c() #Class C
c1.print_p() #Class C
```
> ```Class P```</br>
> ```Class P```</br>
> ```Class C```</br>
> ```Class C```</br>
##
* <font color="#0080FF">**<!!>繼承自父類別的類別變數**</font>
```python=+
c1.z,C.z,P.z
C.z = 'Bonjour' #C原本沒有z變數,會建立出一個類別變數(和P的不同)
c1.z,C.z,P.z
c1.z = 'Ciao' #c1原本沒有z變數,會建立出一個物件變數
c1.z,C.z,P.z
```
> ```('Hello', 'Hello', 'Hello')```</br>
> ```('Bonjour', 'Bonjour', 'Hello')```</br>
> ```('Ciao', 'Bonjour', 'Hello')```
## 7.類別基礎的重點複習
* <font color="#0080FF">**繼承包含類別方法、靜態方法的綜合應用**</font>
```python=+
class Shape:
def __init__(self,x,y):
self.x = x
self.y = y
def move(self,delta_x,delta_y):
self.x = self.x + delta_x
self.y = self.y + delta_y
class Circle(Shape):
pi = 3.14159
all_circles = []
def __init__(self,r = 1,x = 0,y = 0):
super().__init__(x,y)
self.radius = r
self.all_circles.append(self)
@staticmethod
def circle_area(radius): #靜態方法不需要self或cls參數
return Circle.pi * radius * radius
@classmethod
def total_area(cls): #類別方法會傳遞類別本身作為第一個參數
total = 0
for c in cls.all_circles:
total = total + cls.circle_area(c.radius)
return total
```
##
* <font color="#0080FF">**綜合測試**</font>
> <font color="#EA0000">**共用不傳值的方法 -> 推薦使用「類別方法」**</font>
> <font color="#EA0000">**共用需運算的方法 -> 推薦使用「靜態方法」**</font>
```python=+
c1 = Circle()
c1.radius,c1.x,c1.y
c2 = Circle(2,1,1)
c2.radius,c2.x,c2.y
c2.move(2,2)
c2.radius,c2.x,c2.y
Circle.all_circles #就是c1,c2
[c1,c2]
Circle.total_area() #類別方法
c2.total_area() #物件呼叫類別方法
Circle.circle_area(c1.radius) #把靜態方法當公用函式直接呼叫
c1.circle_area(c1.radius)
```
> ```(1, 0, 0)```</br>
> ```(2, 1, 1)```</br>
> ```(2, 3, 3)```</br>
> ```[<__main__.Circle at 0x18ad2617908>, <__main__.Circle at 0x18ad2602f48>]```</br>
> ```[<__main__.Circle at 0x18ad2617908>, <__main__.Circle at 0x18ad2602f48>]```</br>
> ```15.70795```</br>
> ```15.70795```</br>
> ```3.14159```</br>
> ```3.14159```
## 8.私有變數與私有方法
| 類別 | 說明 |
| :------: | :-----------: |
| _前單底線 | 是私有變數、方法</br>但這是約定成俗的作法,使用者依舊可以使用 obj._x 來存取該變數</br>( 使用者應依舊視其為私有變數,不要隨便從外部存取 ) |
| __前雙底線 | 是私有變數、方法</br>外部無法透過 obj.__x 來存取,也可避免繼承時的名稱衝突 |
##
* <font color="#0080FF">**簡單的類別定義**</font>
```python=+
class Mine:
def __init__(self):
self.x = 2
self.__y = 3
def print_y(self):
print(self.__y)
m = Mine()
print(m.x)
print(m.__y) #直接存取私有變數,引發錯誤!!
m.print_y() #print_y不是私有方法
```
> ```2```</br>
> ```AttributeError: 'Mine' object has no attribute '__y'```</br>
> ```3```
##
* <font color="#0080FF">**修飾名稱**</font>
> <font color="#EA0000">**Python 在真正執行程式碼時,會在私有方法和私有變數的名稱前面加上『_類別名稱』。如 : 將「_Mine」拼接成「_Mine__y」是為了防止繼承的名稱衝突,因此繼承的上下類別間可以有同名的私有變數與方法。若故意模擬這種拼接方式來強行存取也是可行的 (Java 或 其他程式語言通常不行)**</font>
```python=+
dir(m)
```
> ```['_Mine__y','__class__','__delattr__','__dict__','__dir__','__doc__','__eq__',...,'print_y','x']```
## 9.Python 中的 getter 與 setter
* <font color="#0080FF">**常見的 getter & setter**</font>
```python=+
class Hero:
def __init__(self):
self._ID = "SUSHI"
def get_id(self):
return self._ID
def set_id(self,new_id):
self._ID = new_id
p1 = Hero()
p1.get_id()
p1.set_id("ZEOxO")
p1.get_id()
```
> ```SUSHI```</br>
> ```ZEOxO```
##
* <font color="#0080FF">**<!!>以 @property 修飾器來實作更靈活的物件變數**</font>
```python=+
"""
@property可以將 method 變成物件變數
"""
class Temperature:
def __init__(self):
self._temp_fahr = 0
#取代getter
@property
def temp(self):
#自動將 *華氏溫度 轉換為 *攝氏溫度
return (self._temp_fahr - 32) * 5 / 9
#定義setter名稱要和getter保持不變 = temp
@temp.setter
def temp(self,new_temp):
#自動將 *攝氏溫度 轉換為 *華氏溫度
self._temp_fahr = new_temp * 9 / 5 + 32
t = Temperature()
t._temp_fahr
t.temp #getter
t.temp = 34 #setter
t._temp_fahr
t.temp
```
> ```0```</br>
> ```-17.77777777777778```</br>
> ```93.2```</br>
> ```34.0```
## 時間戳記
> [name=ZEOxO][time=Wed, Sep 9, 2020 16:12 PM][color=#907bf7]