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