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