---
# System prepended metadata

title: n.Python - 類別與物件導向程式設計(Object_Oriented_Programming)
tags: [Python]

---

# 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]