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