# Class & OOP onion 陳以哲 ---- ## Outline - What is class? - Attribute - Method - OOP - Encapsulation - Inheritance - Polymorphism ---- ### Disclaimer - 這堂課非常難,請緊握扶手站穩踏階 - 這堂課非常難,會有一些內容經過簡化 - 這堂課非常難,課中課後有問題要問 --- ## 類別 class ---- 如果想要存入食物的各種資料 你會怎麼做? ```python! # [name, price, weight] food = [["bread", 40, 100], ["croissant", 100, 150]] ``` --- ```python! # {name : [price, weight]} food = {"bread": [40, 100], "croissant" : [100, 150]} ``` ---- ### Class (類別) - 我們可以用一個 class,來產生物件 (object) - class 像是物件的食譜,說明物件具有哪些構造 <!-- .element: class="fragment" data-fragment-index="1" --> - 每個用 class 產生的物件就稱作實體 (instance) <!-- .element: class="fragment" data-fragment-index="2" --> class 是個食譜! <!-- .element: class="fragment" data-fragment-index="3" --> ---- class 基本語法 ```python= class Food(): # 宣告,習慣首字大寫 pass # class 的內部細節 bread = Food() # 實體化,建立實體 ``` $\overset{\text{類別}}{\text{Food}} \stackrel{實體化}{\rightarrow} \overset{\text{實體}}{\text{bread}}$ <!-- .element: class="fragment" data-fragment-index="1" --> ---- Class 是**屬性**、**方法**的集合 | | 變數 variable | 函數 function | | -------- | -------- | -------- | | | 屬性 attribute | 方法 method | 也就是說這個說明書有很多變數和函數 <!-- .element: class="fragment" data-fragment-index="1" --> --- ## 屬性 Attribute ---- ### 屬性 Attribute ```python! class Food(): def __init__(self): # 要加一個位置參數 (self),代表實體本身 self.price = 40 # 透過初始化函式 (__init__) 設定屬性 bread = Food() print(bread.price) # 用 "." 取值 ``` ---- ### \_\_init__() ```python= class Food(): def __init__(self): # 要加一個位置參數 (self),代表實體本身 self.price = 40 # 透過初始化函式 (__init__) 設定屬性 ``` 在實體化 class 時,會自動執行 `__init()__` ---- ### \_\_init__() ```python= class Food(): def __init__(self): # 來看看這個 self 是什麼 print(self) print(type(self)) print(id(self)) # 用 id 來確認是同一個物件 bread = Food() print(id(bread)) ``` ``` <__main__.Food object at 0x000001EB1D5EBFB0> <class '__main__.Food'> 2109321691056 2109321691056 ``` `self` 是 Food 這個 class 所產生的實體 ---- ### \_\_init__() ```python= class Food(): def __init__(self, price): self.price = price cupcake = Food(50) print(cupcake.price) # 50 ``` 實體化時傳入的參數會自動傳到 `__init__()` 內 傳入的第一個參數對應 `__init__()` 的第二個參數 e.g., 50 $\Rightarrow$ `price` ---- ### \_\_init__(): self 是什麼? ```python= class Food(): def __init__(self, price): self.price = price cupcake = Food(50) print(cupcake.price) # 50 croissant = Food(100) print(croissant.price) # 100 ``` **self 就是實體本身** ---- ### \_\_init__(): 更多屬性 ```python= class Food(): def __init__(self, price, size, rating): self.price = price self.size = size self.rating = rating cupcake = Food(50, "Medium", 100) print(cupcake.size, cupcake.price) # Medium 50 ``` ---- ### 改變屬性的值 ```python= class Food(): def __init__(self, price, size, rating): self.price = price self.size = size self.rating = rating cupcake = Food(50, "Medium", 100) print(cupcake.price) # 50 cupcake.price = 60 print(cupcake.price) # 60 ``` ---- ### 刪除 ```python! class Food(): def __init__(self, price): # 加入屬性的方法 self.price = price bread = Food(40) cupcake = Food(50) del cupcake # 刪除物件 del bread.price # 刪除屬性 print(bread.price) # 屬性被刪除了,會壞掉 ``` ---- ### Class Attribute - 剛剛使用的 `self.price` 是 instance attribute - 屬於實體本身的屬性 - class attribute 是屬於 class 的屬性 ```python! class Food(): sold = 0 # class attribute def __init__(self, price): self.price = price Food.sold += 1 # 注意到這個 sold 跟著 Food bread = Food(40) print(Food.sold) # 1 cupcake = Food(50) print(Food.sold) # 2 ``` ---- ### Class Attribute (補充) - 也可以用實體存取 class attribute - 但如果透過實體改變 class attribute,該 attribute 就會變成實體自己獨立的 attibute ```python! print(bread.sold) #2 bread.sold += 4 print(bread.sold) #6 print(cupcake.sold) #2 print(Food.sold) #2 donut = Food(30) print(bread.sold) #6 print(cupcake.sold) #3 print(Food.sold) #3 ``` ---- ### 練習時間 1. 利用[寶可夢](https://www.pokemon.com/us/pokedex)圖鑑,紀錄 (三個以上) 寶可夢的名稱、編號、體重和屬性 (type) 2. 使用 class attribute 紀錄神奇寶貝物件的數量 ```python! class Pokemon: def __init__(self, name: str, id: int, weight: float, type: list[str]): pass ``` --- ## 方法 Method ---- ### 方法 Method 物件可以放資料 (aka 屬性) 也可以放函數 (aka 方法) ---- ### 方法 Method ```python! class Food(): def __init__(self, price): self.price = price def check_price(self): # 這是一個 method if self.price > 50: print("so expensive") croissant = Food(100) croissant.check_price() # so expensive ``` ---- ### Instance Method - Instance Method 是針對個別實體的 Method - Python 會把呼叫 instance method 的實體自動傳入第一個參數 (self) ```python! class Food(): def __init__(self, price): self.price = price def modify_price(self, factor): self.price = factor * self.price croissant = Food(100) croissant.modify_price(4) print(croissant.price) # 400 ``` ---- ### Instance Method - `__init__()` 其實就是實體化時會自動呼叫的 instance method - instance method 可以傳入其他物件或參數 ```python! class Food(): def __init__(self, price, rating): self.price = price self.rating = rating def compare(self, other): return self.rating > other.rating croissant = Food(100, 80) bread = Food(40, 70) print(croissant.compare(bread)) # True ``` ---- ### Class Method - class method 是針對一個 class 的 method - Python 會把呼叫 class method 的 class 自動傳入第一個參數 (`cls`) - 可以影響到 class 本人和實體化的 object ```python! class Food(): sold = 0 # 這是個 class attribute def __init__(self, price): self.price = price Food.sold += 1 @classmethod # 這個叫做 decorator def foodSold(cls): # 這是個 class method,這邊放的是 cls! print(f"Number of food sold : {cls.sold}") Food.foodSold() #0 croissant = Food(100) Food.foodSold() #1 ``` ---- ### Class Method - `@classmethod` 叫做 decorator,簡單來說是個吃函數的函數,可以給函數加一點魔法 - decorator 自動幫我們把 `foodSold(cls)` 這個 function 所屬的 class 傳給 `foodSold(cls)` 當中的 `cls` 參數 - 所以 `Food` 這個 class 被傳入 `cls` 參數了 ```python! class Food(): @classmethod # 這個叫做 decorator def foodSold(cls): # 這是個 class method,這邊放的是 cls! print(f"Number of food sold : {cls.sold}") ``` ---- ### Static Method 有時候你單純想要把一堆東西包在一起 static method 就很好用,不傳入物件或類別 ```python! class Food(): sold = 0 def __init__(self, price): self.price = price Food.sold += 1 @staticmethod # 又是 decorator def ahhhh(): # 這是個 static method,沒有 self! print("我的豆花!!!30 塊!") tofu_pudding = Food(30) tofu_pudding.ahhhh() # 透過 object 呼叫 Food.promote() # 透過 class 呼叫 ``` ---- | | Instance Method | Class Method | Static Method | | ---- | --------------- | ------------ | ------------------------- | | whom | object | class | 無 | | param | `func(self)` | `func(cls)` | `func()` | | call | `obj.func()` | `cls.func()` | `obj.func()`/`cls.func()` | ---- ### Method 沒填 self 會怎樣 ```python= class Food(): def __init__(self, price): self.price = price def price_now(): print(self.price) croissant = Food(100) croissant.price_now() ``` ``` TypeError: price_now() takes 0 positional arguments but 1 was given ``` Python 會把呼叫 instance method 的實體自動傳入第一個參數 (self) ---- ### 不是 self 也沒關係 ```python= class Food(): def __init__(hahaha, price): hahaha.price = price def price_now(hahaha): print(hahaha.price) croissant = Food(100) croissant.price_now() # 100 ``` 實務上建議全用 `self` ---- ### 練習時間 寫出下列的 instance method 與 class method - `commonType(self, other: Pokemon) -> List` - 回傳兩隻寶可夢共同擁有的屬性 - 若無則回傳 `[]` - `hasType(self, type: str) -> bool` - 判斷寶可夢是否擁有傳入的 `type` - `mostType(cls) -> None` - print 出現最多次的屬性 <!-- 20 min up --> ---- ### Magic Method (補充) - 運算子們 (+、-、<、>=) 和 `int()`、`str()` 等等的背後其實是用 class 實作的 - 這些 method 稱作 magic method - 我們可以為自己的 class 定義 magic method,讓實體支援運算子的操作 ---- ### Magic Method (補充) ```python! class Food(): def __init__(self, name, price): self.name = name self.price = price def __add__(self, other): # 支援 + return self.price + other.price def __str__(self): # 支援 str() return f"{self.name} costs {self.price}" pistachio = Food("pistachio", 30) croissant = Food("croissant", 100) print(pistachio + croissant) # 130 print(croissant) # croissant costs 100 ``` ---- ### \_\_call__() (補充) - 在實體被當作函數呼叫時執行 ```python! class Food(): def __init__(self, name, price): self.name = name self.price = price def __call__(self): print(f"{self.name} was eaten") croissant = Food("croissant", 100) croissant() # croissant was eaten ``` ---- ### 練習時間 延續上次的練習,請寫出下列的 magic method - `__lt__(self, other: Pokemon) -> bool` - 用 (<) 比較兩隻寶可夢的體重 - `__gt__(self, other: Pokemon) -> bool` - 用 (>) 比較兩隻寶可夢的體重 --- ## OOP ---- ### OOP - Object Oriented Programming,物件導向設計 - 把東西包成物件方便管理、使用 - 善用 class、object - 三大特性:封裝、繼承、多型 ---- ## 所有東西都是物件 Everything is an object ---- ### 一些 :chestnut: ```python! x = 10 x.bit_length() # method! ``` 變數是個物件,`x` 屬於 class `int` ---- ### 一些 :chestnut: ```python! def func(): pass print(func.__name__) # attribute! ``` 函數是個物件,屬於 class `function` ---- ### 一些 :chestnut: ```python! class Food(): def __init__(self, price): self.price = price croissant = Food(100) ``` `croissant` 顯然是個物件,屬於 class `Food` --- ## 封裝 Encapsulation ---- ### 封裝 Encapsulation - 將 attribute 分為 public 和 private - private attribute 前有雙底線 - 只能透過 method 取值、修改 ```python! class Profile(): def __init__(self, name, assets): self.name = name self.__assets = assets # 你的錢錢 def get_assets(self): return self.__assets member = Profile("onion", 60) print(member.get_assets()) # 60 print(member.__assets) # AttributeError ``` ---- ### 封裝 Encapsulation - private attribute 前有雙底線 - 只能透過 method 取值、修改 ```python! class Profile(): def __init__(self, name, assets): self.name = name self.__assets = assets # 你的錢錢 def get_assets(self): return self.__assets def deposit(self, num): self.__assets += num member = Profile("onion", 60) member.deposit(1500) print(member.getAssets()) # 1560 member.__assets += 1000000 # AttributeError ``` --- ## 繼承 Inheritance ---- ### 繼承 Inheritance ```python! class Person(): def __init__(self, name, age): self.name = name self.age = age def printInfo(self): print(self.name, self.age) ``` 我們已經有一個 `Person` 的 class 如果我們還需要 `Student` 和 `Teacher` 怎麼辦? ---- ### 繼承 Inheritance class 的強大在於可擴充性 可以繼承之前寫過的 class 繼承別人的 class 叫做 "child" 被繼承的叫做 "parent" ---- ### 繼承 Inheritance 「子類別」繼承「父類別」後 子類別就可以有父類別的**方法**與**屬性** 這讓我們不用重複寫已經寫過的東西 `printciple of python: Don't Reapeat Yourself` ---- ### 繼承 Inheritance ```python! class Student(Person): # Person 是 parent,Student 是 child def __init__(self, name, age, score): super().__init__(name, age) # 存取 parent 的 __init__() self.score = score def isQualified(self): return self.score > 60 student1 = Student("Alice", 18, 70) student1.printInfo() print(student1.isQualified()) ``` 可以用 `super()` 存取 parent 的 method --- ## 多型 Polymorphism ---- ### 多型 Polymorphism - 當我們使用繼承時,child 可以自己重新定義 parent 提供的 method - 一個 parent 可以被很多 child class 繼承 <!-- .element: class="fragment" data-fragment-index="1" --> - 透過這個方式讓這些 class 的物件執行相同名稱,但不同效果的 method -> 多型 (polymorphism) <!-- .element: class="fragment" data-fragment-index="2" --> ---- ```python! class Animal(): def __init__(self): pass def sound(self): return "Ahhhh" class Cow(Animal): def __init__(self): super().__init__() def sound(self): return "MooMoo" class Bird(Animal): def __init__(self): super().__init__() def sound(self): return "ChuChu" def make_sound(obj): print(obj.sound()) ``` ---- Cow 跟 Bird 的 `sound` 可以輸出不同聲音 ```python! mycow = Cow() mybird = Bird() make_sound(mycow) # MooMoo make_sound(mybird) # ChuChu ``` ---- 如果 child 沒有定義 `sound` 會使用 parent 的 `sound` ```python! class Cat(Animal): def __init__(self): super().__init__() mycat = Cat() make_sound(mycat) # Ahhhh ``` --- ## 回家作業 1 [1068. 手刻 Stack](https://tioj.sprout.tw/contests/54/problems/1068) ---- Stack 是什麼? https://sprout.tw/algo2025/ppt_pdf/week01/tp-Data_structure2025.pdf ---- 寫出一個 class: ```python! class Stack(): ``` 完成至少四個方法: ``` __init__(self): 初始化 push(self, item): 把 item 放到 stack 最頂端 pop(self): 把 stack 最頂端的元素刪掉 top(self): return 最頂端的元素 ``` <small>Note: 只有 `top()` 需要 return 其他方法都不用 print 也不用 return</small> ---- ## 回家作業 2 [1069. 股神成長路](https://tioj.sprout.tw/contests/54/problems/1069) --- ## Thank you credit: 2025 Py 班 黃千睿 <style> @import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Noto+Sans+TC:wght@100..900&display=swap'); .reveal-viewport { /* Set your light background here */ background: #fdf6e3 !important; backdrop-filter: none; } :root { /* Use these specific Reveal.js variables for text colors */ --r-main-color: #222; --r-heading-color: #111; --r-link-color: #0056b3; --r-code-font: "JetBrains Mono", monospace; --r-main-font: "Inter", "Noto Sans TC", sans-serif; --r-heading-font: "Inter", "Noto Sans TC", sans-serif; --r-heading-text-transform: none; --r-main-font-size: 32px; } .reveal-viewport { background-color: #f4f4f4; } .reveal .slides { text-align: left; } .reveal .slides .slide:not(.title-slide) { height: 80%; } .reveal .slides .slide .slide-body { height: 80%; display: flex; flex-direction: column; justify-content: center; } .reveal div.sourceCode { margin: 0; min-height: 0; background: transparent; display: flex; flex-direction: column; } .reveal pre { all: unset; min-height: 0; display: flex; flex-direction: column; } .reveal code { font-size: .8em; padding: .2em; border: 1px solid #ccc; border-radius: .2em; background-color: #eee; color: #333; } .reveal .sourceCode code { font-size: .6em; padding: 1em; overflow: scroll; } .reveal blockquote { display: flex; flex-direction: column; background: transparent; box-shadow: none; } .reveal blockquote p { margin: 0; } .reveal blockquote::before { content: "❝"; font-size: 2em; line-height: 0; margin-left: -1em; margin-bottom: -.25em; } .reveal blockquote::after { content: "❞"; font-size: 2em; line-height: 0; text-align: right; } .reveal blockquote + p { text-align: right; } </style>
{"description":"onion 陳以哲","title":"Python Class & OOP","contributors":"[{\"id\":\"069820a6-3e96-4d49-99f2-2503b2c47d84\",\"add\":19191,\"del\":4337,\"latestUpdatedAt\":1774848008812}]"}
    71 views