# Class
資訊之芽 2023 Python 語法班
Author: Sean 韋詠祥
Note:
日期:2023-05-07
課程時間:14:15 - 15:30
大作業寫過 class 了
### Time
14:15 開場
14:25 什麼是 Class
14:30 名詞解釋
14:35 課堂練習(一)
14:50 繼承
14:55 什麼是 `super()`
15:00 attribute 層級比較
15:10 常見錯誤
15:15 魔術方法
15:20 課堂練習(二)
15:30 參考資料 / 延伸閱讀
----
## 在開始之前
- 打開這份簡報
- https://hackmd.io/@Sean64/py-class
- 開啟 [程式碼小抄](https://hackmd.io/@Sean64/py-class-codes) 頁面
- 準備好 VS Code 環境
----
## 如果我們想要紀錄....
```python
speaker_name = 'Sean Wei'
speaker_birthday = 'June'
speaker_skills = ['Cybersecurity', 'Diving']
```
----
## 加上第二個人
```python
speaker1_name = 'Sean Wei'
speaker1_birthday = 'June'
speaker1_skills = ['Cybersecurity', 'Diving']
```
```python
speaker2_name = 'William Mou'
speaker2_birthday = 'October'
speaker2_skills = ['Photography', 'Supercomputing']
```
----
## 如果還有第三個人
```python
speaker1_name = 'Sean Wei'
speaker1_birthday = 'June'
speaker1_skills = ['Cybersecurity', 'Diving']
```
```python
speaker2_name = 'William Mou'
speaker2_birthday = 'October'
speaker2_skills = ['Photography', 'Supercomputing']
```
```python
speaker3_name = 'Sirius Koan'
speaker3_birthday = 'July'
speaker3_skills = ['Softball', 'Mail']
```
越來越多,有點麻煩...
----
## 透過 dict 化簡
```python
speaker1 = {
'name': 'Sean Wei',
'birthday': 'June',
'skills': ['Cybersecurity', 'Diving']
}
```
```python
speaker2 = {
'name': 'William Mou',
'birthday': 'October',
'skills': ['Photography', 'Supercomputing']
}
```
```python
speaker3 = {
'name': 'Sirius Koan',
'birthday': 'July',
'skills': ['Softball', 'Mail']
}
```
---
## 什麼是 Class
- 中文:類別
- 自己創造一個物件(資料型態)
- 把函式跟變數結合的工具
```python
# 定義方式
class Speaker:
pass
```
----
## 如何使用 Class
跟函式定義的方式很像
```python
class Speaker:
pass
```
```python
speaker1.name = 'Sean Wei'
speaker1.birthday = 'June'
speaker1.skills = ['Cybersecurity', 'Diving']
```
```python
speaker2.name = 'William Mou'
speaker2.birthday = 'October'
speaker2.skills = ['Photography', 'Supercomputing']
```
看起來似乎沒有比較簡潔
----
## 正確的寫法
```python
class Speaker:
def __init__(self, name, birthday, skills):
self.name = name
self.birthday = birthday
self.skills = skills
```
```python
speaker1 = Speaker('Sean Wei', 'June',
['Cybersecurity', 'Diving'])
```
```python
speaker2 = Speaker('William Mou', 'October',
['Photography', 'Supercomputing'])
```
```python
speaker3 = Speaker('Sirius Koan', 'July',
['Softball', 'Mail'])
```
----
## 名詞解釋
- `method` 方法:class 裡面的 function
- `instance` 實體:依照 class 做出來的物件
- `attribute` 屬性:class 裡面的變數
----
## `self`
- class 中 method 的參數至少要有 self
- 決定要設定哪一個物件
----
## `__init__()`
- class 中一個特殊的 method
- 是一個 constructor 建構子,初始化的時候用
- 使用 `obj = classname()` 建構時會被呼叫
- 一定要有 self 做為參數
----
## 課堂練習(一)
幫 `class Speaker` 實作 `learn()` 函式
讓講師向另一位講師學習相關技能
```python
class Speaker:
def __init__(self, name, birthday, skills):
self.name = name
self.birthday = birthday
self.skills = skills
def learn(self, teacher):
print(f'{self=}, {self.skills=}')
print(f'{teacher=}, {teacher.skills=}')
# Write your code here
```
```python
speaker1 = Speaker('Sean Wei', 'June', ['Cybersecurity', 'Diving'])
speaker2 = Speaker('William Mou', 'October', ['Photography', 'Supercomputing'])
speaker1.learn(speaker2)
```
Note:
Answer: `self.skills.extend(teacher.skills)`
---
## 繼承
Inheritance
----
## 有兩種身份
分別定義 Student 及 Employee
```python
class Student:
def __init__(self, name, age, student_id):
self.name = name
self.age = age
self.student_id = student_id
def say_hi():
print(f'Hello, I am {name}!')
```
```python
class Employee:
def __init__(self, name, age, employee_id):
self.name = name
self.age = age
self.employee_id = employee_id
def say_hi():
print(f'Hello, I am {name}!')
```
----
## 抽取重複部分
核心概念:DRY(Don’t Repeat Yourself)
```python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hi():
print(f'Hello, I am {name}!')
```
```python
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age)
self.student_id = student_id
```
```python
class Employee(Person):
def __init__(self, name, age, employee_id):
super().__init__(name, age)
self.employee_id = employee_id
```
----
## 什麼是 `super()`
- 代表父類別
- 透過 `super()` 執行上層函式
- 放在函式開頭、中間、結尾都可以
----
## 語法結構
```python
class NormalClass:
pass
class NormalClass():
pass
```
```python
class DerivedClass(BaseClass):
pass
class DerivedClass(moduleName.BaseClass):
pass
```
```python
class DerivedClass(Base1, Base2, Base3):
pass
```
----
## 對於多層繼承的 super() 呢?
```python
class First:
def __init__(self):
print('Initializing first class')
# super().__init__() # no need
```
```python
class Second(First):
def __init__(self):
print('Initializing second class')
super().__init__()
```
```python
class Third(Second):
def __init__(self):
print('Initializing third class')
super().__init__()
```
```python
foo = Third()
```
----
## 繼承多個類別的 super()
```python
class Foo:
def __init__(self):
print('Initializing foo class')
super().__init__() # note here
```
```python
class Bar:
def __init__(self):
print('Initializing bar class')
# super().__init__() # no need
```
```python
class Test(Foo, Bar):
def __init__(self):
print('Initializing test class')
super().__init__()
```
```python
baz = Test()
```
----
## 特殊變數 `__mro__`
Method Resolution Order
```python
print(Student.__mro__)
```
```python
print(Third.__mro__)
```
```python
print(Test.__mro__)
```
---
## attribute 層級比較
class-level vs instance-level
----
## 建立 Phone 類別
```python
class Phone:
pwd = '0000'
foo = Phone()
bar = Phone()
```
```python
print(f'{Phone.pwd=}, {foo.pwd=}, {bar.pwd=}')
```
Output: `Phone.pwd='0000', foo.pwd='0000', bar.pwd='0000'`<!-- .element: class="fragment" data-fragment-index="1" -->
----
## 修改 foo 密碼
```python
foo.pwd = '1234'
print(f'{Phone.pwd=}, {foo.pwd=}, {bar.pwd=}')
```
Output: `Phone.pwd='0000', foo.pwd='1234', bar.pwd='0000'`<!-- .element: class="fragment" data-fragment-index="1" -->
----
## 修改 Phone 密碼
```python
Phone.pwd = '5678'
print(f'{Phone.pwd=}, {foo.pwd=}, {bar.pwd=}')
```
Output: `Phone.pwd='5678', foo.pwd='1234', bar.pwd='5678'`<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
問:為什麼 foo 沒有跟著改
----
## 查看 attribute
可以用 `vars()` 函式
```python
print(vars(Phone)) # {'__module__': '__main__',
# 'pwd': '5678', '__dict__': ....}
print(vars(foo)) # {'pwd': '1234'}
print(vars(bar)) # {}
```
或是透過 `__dict__` 取得
```python
# Same as above
print(Phone.__dict__)
print(foo.__dict__)
print(bar.__dict__)
```
----
## 設定顏色
```python
Phone.color = 'blue'
print(f'{Phone.color=}, {foo.color=}, {bar.color=}')
```
Output: `Phone.color='blue', foo.color='blue', bar.color='blue'`<!-- .element: class="fragment" data-fragment-index="1" -->
----
## 設定價格
```python
foo.price = 34_900
print(f'{Phone.price=}, {foo.price=}, {bar.price=}')
```
Output: `AttributeError`<!-- .element: class="fragment" data-fragment-index="1" -->
---
## 常見錯誤
為什麼我的 list 不乖
Note:
9.3.5. Class and Instance Variables
----
## 直觀的寫法
```python
class Person:
skills = []
# Note here
def learn(self, skill):
self.skills.append(skill)
```
```python
sean = Person()
sean.learn('Cybersecurity')
sean.learn('Diving')
print(f'{sean.skills=}')
```
```python
mou = Person()
mou.learn('Photography')
mou.learn('Supercomputing')
print(f'{mou.skills=}')
```
----
## 正確寫法
```python
class Person:
def __init__(self):
# creates a new empty list for each person
self.skills = []
def learn(self, skill):
self.skills.append(skill)
```
```python
sean = Person()
sean.learn('Cybersecurity')
sean.learn('Diving')
print(f'{sean.skills=}')
```
```python
mou = Person()
mou.learn('Photography')
mou.learn('Supercomputing')
print(f'{mou.skills=}')
```
---
## 魔術方法
Magic methods
----
## 讓我們為食物建立 class
```python
class Food:
def __init__(self, price, calorie):
self.price = price # Unit: NTD
self.calorie = calorie # Unit: kcal
```
```python
chicken_nuggets = Food(65, 260)
french_fries = Food(65, 530)
oreo_flurry = Food(55, 360)
corn_soup = Food(40, 90)
```
----
## 印出價格、熱量
```python
class Food:
# ...
def __str__(self):
return 'price and calorie is xxx' # 小練習
```
```python
# 記得先重新執行 xxx = Food(x, x) 語句
print(french_fries)
# price = 65 NTD, calorie = 260 kcal
```
----
## 把食物混在一起
```python
class Food:
# ...
def __add__(self, other):
price = 0 # 請完成
calorie = 0 # 請完成
mixture = Food(price, calorie)
return mixture
```
```python
# 記得先重新執行 xxx = Food(x, x) 語句
oreo_nuggets = oreo_flurry + chicken_nuggets
print(oreo_nuggets)
# price = 120 NTD, calorie = 620 kcal
```
----
## 課堂練習(二)
把 `class Food` 實作得更完整
```python
class Food:
# ...
def __mul__(self, num):
pass # 計算 num 份食物的價格、熱量
def __truediv__(self, num):
pass # 平分給 num 個人後每份長怎樣
```
```python
def __eq__(self, other):
pass # 比較「💲價格」相不相同,暫時無視熱量
def __gt__(self, other):
pass # 比較「🔥熱量」是否更高,暫時無視價格
# (optional) 針對「💲價格」,除了 eq 相等之外,實作 ne 不相等
# (optional) 針對「🔥熱量」,除了 gt 外,實作 lt 小於、ge、le
```
---
# Thanks
投影片連結:https://hackmd.io/@Sean64/py-class
<!-- .element: class="r-fit-text" -->
<br>
[![CC-BY](https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by.png)](https://creativecommons.org/licenses/by/4.0/deed.zh_TW)
###### 這份投影片以 [創用 CC - 姓名標示](https://creativecommons.org/licenses/by/4.0/deed.zh_TW) 授權公眾使用,原始碼及講稿請見 [此連結](https://hackmd.io/@Sean64/py-class/edit)。
----
## 參考資料 / 延伸閱讀
- 歷年資芽講義:[Py 2022](https://sprout.tw/py2022/slides), [Py 2021](https://sprout.tw/py2021/#!slides.md)
- [三分鐘學會 Class 運用](https://youtu.be/dQw4w9WgXcQ)
- [關於物件導向的繼承](https://medium.com/seaniap/py-f93028056d9b)
- PyDoc: [Class tutorial](https://docs.python.org/3/tutorial/classes.html), [Class reference](https://docs.python.org/3/reference/compound_stmts.html#class-definitions)
- Private Variables
- `@Decorator`
- `@dataclass`
{"metaMigratedAt":"2023-06-18T03:16:12.803Z","metaMigratedFrom":"YAML","title":"Class - 資訊之芽 2023 Python 語法班","breaks":true,"description":"Sean 韋詠祥 / 2023-05-07 14:15 / 什麼是 Class / 繼承 / attribute 層級比較 / 常見錯誤 / Private Variable / Decorator / dataclass","contributors":"[{\"id\":\"8a6148ae-d280-4bfd-a5d9-250c22d4675c\",\"add\":28483,\"del\":17851}]"}