changed 2 years ago
Published Linked with GitHub

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 參考資料 / 延伸閱讀


在開始之前


如果我們想要紀錄

speaker_name = 'Sean Wei'
speaker_birthday = 'June'
speaker_skills = ['Cybersecurity', 'Diving']

加上第二個人

speaker1_name = 'Sean Wei'
speaker1_birthday = 'June'
speaker1_skills = ['Cybersecurity', 'Diving']
speaker2_name = 'William Mou'
speaker2_birthday = 'October'
speaker2_skills = ['Photography', 'Supercomputing']

如果還有第三個人

speaker1_name = 'Sean Wei'
speaker1_birthday = 'June'
speaker1_skills = ['Cybersecurity', 'Diving']
speaker2_name = 'William Mou'
speaker2_birthday = 'October'
speaker2_skills = ['Photography', 'Supercomputing']
speaker3_name = 'Sirius Koan'
speaker3_birthday = 'July'
speaker3_skills = ['Softball', 'Mail']

越來越多,有點麻煩


透過 dict 化簡

speaker1 = {
	'name': 'Sean Wei',
	'birthday': 'June',
	'skills': ['Cybersecurity', 'Diving']
}
speaker2 = {
	'name': 'William Mou',
	'birthday': 'October',
	'skills': ['Photography', 'Supercomputing']
}
speaker3 = {
	'name': 'Sirius Koan',
	'birthday': 'July',
	'skills': ['Softball', 'Mail']
}

什麼是 Class

  • 中文:類別
  • 自己創造一個物件(資料型態)
  • 把函式跟變數結合的工具
# 定義方式
class Speaker:
    pass

如何使用 Class

跟函式定義的方式很像

class Speaker:
    pass
speaker1.name = 'Sean Wei'
speaker1.birthday = 'June'
speaker1.skills = ['Cybersecurity', 'Diving']
speaker2.name = 'William Mou'
speaker2.birthday = 'October'
speaker2.skills = ['Photography', 'Supercomputing']

看起來似乎沒有比較簡潔


正確的寫法

class Speaker:
    def __init__(self, name, birthday, skills):
        self.name = name
        self.birthday = birthday
        self.skills = skills
speaker1 = Speaker('Sean Wei', 'June',
                   ['Cybersecurity', 'Diving'])
speaker2 = Speaker('William Mou', 'October',
                   ['Photography', 'Supercomputing'])
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() 函式
讓講師向另一位講師學習相關技能

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

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}!')
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)

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_hi():
        print(f'Hello, I am {name}!')
class Student(Person):
    def __init__(self, name, age, student_id):
        super().__init__(name, age)
        self.student_id = student_id
class Employee(Person):
    def __init__(self, name, age, employee_id):
        super().__init__(name, age)
        self.employee_id = employee_id

什麼是 super()

  • 代表父類別
  • 透過 super() 執行上層函式
  • 放在函式開頭、中間、結尾都可以

語法結構

class NormalClass:
    pass

class NormalClass():
    pass
class DerivedClass(BaseClass):
    pass

class DerivedClass(moduleName.BaseClass):
    pass
class DerivedClass(Base1, Base2, Base3):
    pass

對於多層繼承的 super() 呢?

class First:
    def __init__(self):
        print('Initializing first class')
        # super().__init__()  # no need
class Second(First):
    def __init__(self):
        print('Initializing second class')
        super().__init__()
class Third(Second):
    def __init__(self):
        print('Initializing third class')
        super().__init__()
foo = Third()

繼承多個類別的 super()

class Foo:
    def __init__(self):
        print('Initializing foo class')
        super().__init__()  # note here
class Bar:
    def __init__(self):
        print('Initializing bar class')
        # super().__init__()  # no need
class Test(Foo, Bar):
    def __init__(self):
        print('Initializing test class')
        super().__init__()
baz = Test()

特殊變數 __mro__

Method Resolution Order

print(Student.__mro__)
print(Third.__mro__)
print(Test.__mro__)

attribute 層級比較

class-level vs instance-level


建立 Phone 類別

class Phone:
    pwd = '0000'

foo = Phone()
bar = Phone()
print(f'{Phone.pwd=}, {foo.pwd=}, {bar.pwd=}')

Output: Phone.pwd='0000', foo.pwd='0000', bar.pwd='0000'


修改 foo 密碼

foo.pwd = '1234'

print(f'{Phone.pwd=}, {foo.pwd=}, {bar.pwd=}')

Output: Phone.pwd='0000', foo.pwd='1234', bar.pwd='0000'


修改 Phone 密碼

Phone.pwd = '5678'

print(f'{Phone.pwd=}, {foo.pwd=}, {bar.pwd=}')

Output: Phone.pwd='5678', foo.pwd='1234', bar.pwd='5678'

Note:
問:為什麼 foo 沒有跟著改


查看 attribute

可以用 vars() 函式

print(vars(Phone))  # {'__module__': '__main__',
                    #  'pwd': '5678', '__dict__': ....}

print(vars(foo))    # {'pwd': '1234'}
print(vars(bar))    # {}

或是透過 __dict__ 取得

# Same as above
print(Phone.__dict__)
print(foo.__dict__)
print(bar.__dict__)

設定顏色

Phone.color = 'blue'

print(f'{Phone.color=}, {foo.color=}, {bar.color=}')

Output: Phone.color='blue', foo.color='blue', bar.color='blue'


設定價格

foo.price = 34_900

print(f'{Phone.price=}, {foo.price=}, {bar.price=}')

Output: AttributeError


常見錯誤

為什麼我的 list 不乖

Note:
9.3.5. Class and Instance Variables


直觀的寫法

class Person:
    skills = []
    # Note here

    def learn(self, skill):
        self.skills.append(skill)
sean = Person()
sean.learn('Cybersecurity')
sean.learn('Diving')
print(f'{sean.skills=}')
mou = Person()
mou.learn('Photography')
mou.learn('Supercomputing')
print(f'{mou.skills=}')

正確寫法

class Person:
    def __init__(self):
        # creates a new empty list for each person
        self.skills = []

    def learn(self, skill):
        self.skills.append(skill)
sean = Person()
sean.learn('Cybersecurity')
sean.learn('Diving')
print(f'{sean.skills=}')
mou = Person()
mou.learn('Photography')
mou.learn('Supercomputing')
print(f'{mou.skills=}')

魔術方法

Magic methods


讓我們為食物建立 class

class Food:
    def __init__(self, price, calorie):
        self.price = price      # Unit: NTD
        self.calorie = calorie  # Unit: kcal
chicken_nuggets = Food(65, 260)
french_fries = Food(65, 530)
oreo_flurry = Food(55, 360)
corn_soup = Food(40, 90)

印出價格、熱量

class Food:
    # ...

    def __str__(self):
        return 'price and calorie is xxx'  # 小練習
# 記得先重新執行 xxx = Food(x, x) 語句

print(french_fries)
# price = 65 NTD, calorie = 260 kcal

把食物混在一起

class Food:
    # ...

    def __add__(self, other):
        price = 0    # 請完成
        calorie = 0  # 請完成
        mixture = Food(price, calorie)
        return mixture
# 記得先重新執行 xxx = Food(x, x) 語句

oreo_nuggets = oreo_flurry + chicken_nuggets

print(oreo_nuggets)
# price = 120 NTD, calorie = 620 kcal

課堂練習(二)

class Food 實作得更完整

class Food:
    # ...

    def __mul__(self, num):
        pass  # 計算 num 份食物的價格、熱量

    def __truediv__(self, num):
        pass  # 平分給 num 個人後每份長怎樣
    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


CC-BY

這份投影片以 創用 CC - 姓名標示 授權公眾使用,原始碼及講稿請見 此連結

參考資料 / 延伸閱讀

Select a repo