# 從0開始學 Python
## 單元 0:程式語言
- **什麼是程式語言?**
- 程式語言是一種讓電腦執行特定指令的方式,就像我們用語言進行溝通,程式語言則是人與電腦之間的溝通工具。
- 程式碼由一行行的**指令**組成,電腦會**依照順序逐行執行**。
- **程式運行的方式**
- 逐行執行:電腦會從上到下逐行執行指令,若有錯誤則會停止運行並顯示錯誤訊息。
- 控制流程:透過條件判斷(`if-else`)和迴圈(`for`、`while`),可以改變程式執行的順序。
- **程式碼的組織**
- **變數與型別**
變數用來儲存資料,並且可以有不同的型態(型別)
如:**整數**、**小數(浮點數)**、**文字(字串)** 等等
- **輸入與輸出**
提供使用者輸入資料,並且輸出結果給使用者看。
- **資料結構**
利用資料結構可以一次性儲存更多的資料,或者更有結構性的儲存方便管理與操作。
- **運算**
進行數字的算數運算,或是**或、且、非**常見的邏輯運算。
- **條件控制**
給定條件來讓程式跳過特定部分,或是在不同條件執行不同部分。
- **迴圈**
幫助執行需要一直重複的程式碼,包含給定次數與未知次數的重複。
- **函數**
將常用到的功能包裝成一個函數,可以讓程式碼更為整潔、有架構,也可以避免重複寫同一段程式碼。
- **導入函數庫**
可以將別人已經寫好的功能進行導入(import),就不用自己寫。
- **範例程式架構**
```python=
# ---------- Import 區塊 ----------
import random # 匯入 random 模組,用於產生隨機數
import time # 匯入 time 模組,用於延遲輸出
# ---------- 變數宣告區塊 ----------
WELCOME_MESSAGE = "歡迎使用 Python 課程範例程式"
LOOP_COUNT = 3 # 設定迴圈執行次數
# ---------- 函數定義區塊 ----------
def display_message(message):
"""顯示訊息並延遲 0.5 秒,模擬程式逐步執行的過程"""
print(message)
time.sleep(0.5)
def generate_random_numbers(count, start=1, end=100):
"""產生指定數量的隨機數,並回傳列表"""
numbers = []
for _ in range(count):
numbers.append(random.randint(start, end))
return numbers
def analyze_number(num):
"""判斷數字是偶數或奇數,並回傳對應的描述字串"""
if num % 2 == 0:
return f"{num} 是偶數"
else:
return f"{num} 是奇數"
# ---------- 程式執行區塊 ----------
display_message(WELCOME_MESSAGE) # 顯示歡迎訊息
# 產生隨機數列表
random_numbers = generate_random_numbers(LOOP_COUNT, 1, 50)
# 依序分析每個隨機數
for num in random_numbers:
result = analyze_number(num) # 呼叫函數分析數字
display_message(result) # 顯示結果
```
---
## 單元 1:變數與型別
- **變數**:變數是用來儲存資料的容器,使用等號 `=` 進行賦值。
- 範例:`x = 10`
- **變數命名規則**:
- 只能由大寫`[A-Z]`、小寫`[a-z]`、數字`[0-9]`、底線`_`組成。
- 數字不能在開頭
`1num = 10 # 不合法`
- **多字詞的命名通則**
- Camel Case : `firstNameOfStudent`
- Snake Case : `first_name_of_student`
- **型別**:Python 中的資料型別包括:
- 整數(`int`)
- 浮點數(`float`)
- 字串(`str`)
- 布林值(`bool`):`True`, `False`
- **使用 `type()` 函數檢查變數型別。**
```python
a = 10.1
print(type(a)) # 輸出: <class 'float'>
```
- **型別轉換**
- 使用`int()`、`str()`等就可以進行傳換
```python
a = "123"
print(type(a)) # 輸出: <class 'str'>
b = int(a)
print(type(b)) # 輸出: <class 'int'>
```
### 範例練習
```python
# 練習 1: 宣告變數並賦值
x = 25 # 整數
y = "18"
pi = 3.14 # 浮點數
name = "Python" # 字串
# 練習 2: 使用 type() 函數檢查變數型別
print(type(x)) # 輸出: <class 'int'>
print(type(y)) # 輸出: <class 'str'>
print(type(pi)) # 輸出: <class 'float'>
print(type(name)) # 輸出: <class 'str'>
# 練習 3: 轉換變數型別
y = int(y)
print(x+y) # 輸出: 43
```
### 牛刀小試
1. 宣告一個布林值變數 `is_valid`,並賦值為 `True`
2. 創建一個浮點數變數 `temperature`,賦值為 `36.6`,然後使用 `type()` 函數檢查其型別
---
## 單元 2:輸入與輸出
- **輸入(Input)**:使用 `input()` 函數從使用者獲取輸入。
- 輸入的值預設為字串,若需其他型別需進行轉換。
```python
# 輸入字串
name = input("名字:")
# 輸入轉整數
age = int(input("年齡:"))
```
- **輸出(Output)**:使用 `print()` 函數將資料輸出到螢幕。
- 可以使用逗號 `,`進行多項輸出。
```python
name = "Bob"
age = "18"
print("名字:", name, "年齡:", age)
# 輸出: "名字: Bob 年齡: 18"
```
- **f-string**
使用f開頭的字串,可以在裡面加入參數值方便格式化輸出。
```python
name = "Bob"
age = "18"
print(f"名字: {name} 年齡: {age}")
# 輸出: "名字: Bob 年齡: 18"
```
### 範例練習
```python
# 練習 1: 簡單的輸入與輸出
name = input("請輸入你的名字:")
print(f"你好,{name}!")
# 練習 2: 計算兩數相加
num1 = int(input("請輸入第一個數字:"))
num2 = int(input("請輸入第二個數字:"))
sum_result = num1 + num2
print(f"{num1} + {num2} = {sum_result}")
```
### 牛刀小試
1. 寫一個程式,讓使用者輸入兩個整數,輸出兩數的乘積
提示:若要取得兩數乘積,可以使用`*`運算。例如`a*b`
2. 接收使用者輸入的名字和年齡,然後輸出問候語:`你好,XXX,你的年齡是 YYY 歲!`
---
## 單元 3:資料結構
### 陣列(list)
- 陣列使用方括號 `[]`,儲存有序資料。
- **索引值與取值**:
- 陣列中的元素可以透過索引值(從 0 開始)存取。
- 支援負索引,`-1` 表示最後一個元素,`-2` 表示倒數第二個元素。
```python
numbers = [10, 20, 30, 40]
print(numbers[0]) # 輸出: 10
print(numbers[-1]) # 輸出: 40
```
- **基本操作**:
- **新增元素**:`append(value)`
- **插入元素**:`insert(index, value)`
- **刪除特定位置**:`del`
- **刪除特定元素**:`remove()`
- **取得特定區段**:`list[a:b:c]` (後面會再做介紹)
- **常用函數**:
- `split()`
用`split(切割目標)`:切割字串(預設是空格)成陣列
```python
arr = input().split("-")
# 輸入 "1-2-3-4-5"
# arr = ["1", "2", "3", "4", "5"]
```
- `map()`
用`map(函數, 陣列)`可以快速將陣列每一個值帶入函數中並返回,記得要再外包一層`list()`才會是正常的陣列型態。
```python
arr = list(map(int, arr))
print(arr) # 輸出: [1, 2, 3, 4, 5]
```
### 範例練習
```python
numbers = [1, 2, 3]
numbers.append(4) # 新增 4
print(numbers) # 輸出: [1, 2, 3, 4]
numbers.insert(1, 4) # 在索引 1 插入 4
print(numbers) # 輸出: [1, 4, 2, 3, 4]
del numbers[2] # 刪除索引 2 的元素
print(numbers) # 輸出: [1, 4, 3, 4]
numbers[2] = 10 # 修改索引2的值
print(numbers) # 輸出: [1, 4, 10, 4]
numbers.remove(4) # 刪除第一個出現的4
print(numbers) # 輸出: [1, 10, 4]
```
### 雙層(二維)陣列
- 陣列的元素可以是任何東西,當然也可以是一個陣列!
```python
# 定義一個二維陣列 arr
arr = [[1, 2, 3], # arr中的第一個陣列
[4, 5, 6], # arr中的第二個陣列
[7, 8, 9]] # arr中的第三個陣列
# 取得arr中第二個陣列
# v
x = arr[1][0]
# ^
# 取得第1個陣列第一個元素
print(x) # 輸出: 4
```
### 牛刀小試
1. 創建一個陣列 `colors`,包含三個顏色:`red`, `green`, `blue`,並用索引值輸出第二個顏色,接著新增顏色`yellow`並刪除掉`blue`
2. 創建一個`3*4`的二維陣列,依序填入`1~12`的數字,並設法利用索引值取值印出`7`這個數字
---
### 字典(dict)
- 字典使用大括號 `{}`,以鍵值對形式儲存資料。
- 基本操作:
- **新增/修改鍵值對**:`dict[key] = value`
- **刪除鍵值對**:`del dict[key]`
- **取得所有鍵或值**:`keys()`、`values()`
```python
student = {"name": "Alice", "age": 20}
student["grade"] = "A" # 新增鍵值對
student["age"] = 24 # 修改年齡
print(student) # 輸出: {'name': 'Alice', 'age': 24, 'grade': 'A'}
del student["age"] # 刪除 age
print(student) # 輸出: {'name': 'Alice', 'grade': 'A'}
print(list(student.keys())) # 輸出: ['name', 'grade']
print(list(student.values())) # 輸出: ['Alice', 'A']
```
### 牛刀小試
1. 使用字典儲存三個人的資料,key值為名字,value為生日
2. 接續上題,新增一個人的資料,並刪除字典中第一個人的資料
---
## 單元 4:算數運算
- **基本運算符號**:
- 加法:`+`
- 減法:`-`
- 乘法:`*`
- 除法:`/`
- 整數除法:`//`
- 取餘數:`%`
- 次方:`**`
### 範例練習
```python
# 基本算數運算
x = 10
y = 3
print(x + y) # 輸出: 13
print(x - y) # 輸出: 7
print(x * y) # 輸出: 30
print(x / y) # 輸出: 3.333333333333333
print(x // y) # 輸出: 3
print(x % y) # 輸出: 1
print(x ** y) # 輸出: 1000
```
- **賦值運算**
當運算符號後面加上一個`=`,可以在運算過後直接將值賦予給前面的變數。
```python
a = 10
b = 2
a += b # 等價於a = a+b
print(a) # 輸出: 12
b *= a # 等價於b = b*a
print(b) # 輸出: 24
```
### 牛刀小試
1. 給使用者輸入兩個整數,計算前者除以後者的商與餘數
2. 設定變數a b c,輸出$y = ax^2+bx+c$ 方程式中$x$的公式解
$$
\begin{aligned}
x1 &= \frac{-b + \sqrt{b^2-4ac}}{2a} \\[6pt]
x2 &= \frac{-b - \sqrt{b^2-4ac}}{2a}
\end{aligned}
$$
---
## 單元 5:邏輯運算
- **邏輯運算符號**:
- 且(`and`): 需要同時滿足
- 或(`or`) : 滿足其中一個
- 非(`not`): 取反向
- **比較符號**:
- 等於(`==`)
- 不等於(`!=`)
- 大於(`>`)
- 大於等於(`>=`)
- 小於(`<`)
- 小於等於(`<=`)
### 範例練習
```python
# 基本邏輯運算
x = True
y = False
print(x and y) # 輸出: False
print(x or y) # 輸出: True
print(not x) # 輸出: False
a = 10
b = 11
c = 10.0
print(a == b) # 輸出: False
print(a == c) # 輸出: True
print(a <= b) # 輸出: True
```
### 牛刀小試
1. 假設 `a = True` 和 `b = False`,計算 `(a and b) or (not b)` 的結果,並用程式驗證
2. 寫一段程式,檢查一個數字是否大於 10 並且是偶數
---
## 單元 6:if-else 條件控制
- **語法結構**:
```python
if 條件:
# 當條件為 True 時執行的程式碼
elif 其他條件:
# 當其他條件為 True 時執行的程式碼
else:
# 當條件均為 False 時執行的程式碼
```
- **範例**:
```python
x = 10
if x > 10:
print("x 大於 10")
elif x == 10:
print("x 等於 10")
else:
print("x 小於 10")
```
### 範例練習
```python
# 使用 if-elif-else 判斷成績等級
grade = 85
if grade >= 90:
print("A")
elif grade >= 80:
print("B")
elif grade >= 70:
print("C")
elif grade >= 60:
print("D")
else:
print("F")
```
### 牛刀小試
1. 編寫程式判斷一個輸入的整數是否為正數、零或負數
2. 編寫程式判斷一個數字是否是 3 或 5 的倍數,或兩者皆是,或都不是
---
## 單元 7:for 與 while 迴圈
- **for 迴圈**:用於遍歷序列中的每個元素。
```python
for 元素 in 序列:
# 執行的程式碼
```
- 範例
```python
for i in [1, 2, 3]:
print(i)
# 輸出:
# 1
# 2
# 3
```
- **遍歷字典**
- 遍歷字典時會直接遍歷所有的key值
```py
age = {"Alice": 10, "Blice": 13, "Clice": 18}
for d in age:
print(d)
# 輸出:
# Alice
# Blice
# Clice
```
- **使用 `range()`**:
- `range()` 函數用於生成一系列連續的數字,適合用於 for 迴圈。
- 語法:
```python
range(start, stop, step)
```
- 預設值:
- `start` 預設為 0
- `step` 預設為 1
```python
# 使用 range() 生成數字序列
for i in range(5): # 相當於 range(0, 5, 1)
print(i) # 輸出: 0 1 2 3 4
for i in range(2, 5): # 相當於 range(2, 5, 1)
print(i) # 輸出: 2 3 4
for i in range(1, 10, 2):
print(i) # 輸出: 1 3 5 7 9
for i in range(5, -1, -1):
print(i) # 輸出: 5 4 3 2 1 0
```
- 與`range()`相似的,就是前面提到的`list[a:b:c]`
`a, b, c` 等同於 `start, stop, step`,只不過是以**索引值**取得部分陣列
- 有兩種形式`[a:b]`、`[a:b:c]`
- `a`為空就是預設`0`
- `b`為空就是預設陣列最後一個
- `c`不填時用第一種形式`[a:b]`,預設為`1`
```python
arr = [10, 20, 30, 40, 50, 60]
print(arr[:3]) # 輸出 [10, 20, 30]
print(arr[2:]) # 輸出 [30, 40, 50, 60]
print(arr[1:4]) # 輸出 [20, 30, 40]
print(arr[::2]) # 輸出 [10, 30, 50]
```
- **while 迴圈**:當條件為 True 時重複執行。
```python
while 條件:
# 執行的程式碼
```
- 範例:
```python
count = 0
while count < 3:
print(count)
count += 1
```
- **break** 和 **continue**:迴圈控制
- **break**: 當遇到`break`的時候,會立即跳出所在迴圈
```python=
for i in range(5):
print(i)
for j in range(10):
print(i, j)
if j % 3 == 2:
break # 直接跳出j迴圈,並接續執行i迴圈
```
- **continue**: 遇到則跳過此回合,但繼續執行所在迴圈
```python=
i = 0
while i < 10:
i += 1
if i == 3:
continue
print(i)
```
### 範例練習
```python
# 使用 for 迴圈遍歷列表
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# 使用 while 迴圈計算 1 到 5 的總和
sum = 0
num = 1
while num <= 5:
sum += num
num += 1
print("總和:", sum)
```
### 牛刀小試
1. 輸入`n`,使用 `for` 迴圈計算 `1` 到 `n` 的總和
2. 輸入一個數字`n`,重複以下操作:
- 如果他是奇數就將他乘上3再加1
- 如果是偶數就將他除以2
- 持續操作並輸出過程的n值,**直到**他變成1
---
## 單元 8:函數(Function)
### 函數的定義與使用
- 函數是一組執行特定任務的程式碼,可以重複使用
當函數執行到 `return` 時,會立即跳出函數不再執行,並回傳後面接著的值
- 使用 `def` 關鍵字定義函數。
```python
def 函數名稱(參數):
# 函數內的程式碼
...
return 返回值
# 呼叫函數
函數名稱(參數)
```
- **範例 1**:
```python
def add_numbers(a, b):
return a + b
result = add_numbers(5, 3) # 以位置參數帶入
# result 變數會接住函數回傳的值
print(result) # 輸出: 8
result = add_numbers(b=5, a=3) # 以關鍵字參數帶入
# 呼叫進入函數後就會用指定參數名稱帶入,而非按照順序
```
- 函數也可以不用有回傳值,會在函數執行完畢後自動返回 `None`
- **範例 2**:
```python
def say_hi(name):
print(f"Hi {name}! How are you?")
# 輸出: "Hi Eleven! How are you?"
result = say_hi("Eleven")
print(result) # 輸出: None
```
### 範例練習
```python
# 練習 1: 定義函數計算平方
def square(num):
return num ** 2
print(square(4)) # 輸出: 16
# 練習 2: 定義函數判斷是否為偶數
def is_even(num):
if num % 2 == 0:
return True
else:
return False
print(is_even(10)) # 輸出: True
print(is_even(7)) # 輸出: False
```
### 牛刀小試
1. 定義一個函數 `fact(n)`,計算 $n!$ (階乘)
$$
\begin{align}
n! &= 1 \times 2 \times 3 \times ... \times n \\
ex.\ 4! &= 1 \times 2 \times 3 \times 4 = 24
\end{align}
$$
- 定義 $0! = 1$
---
### 函數進階用法
- **lambda 函數(匿名函數)**:
- 使用 `lambda` 關鍵字定義一個簡單的匿名函數。
- 語法:
```python
lambda 參數1, 參數2, ... : 表達式
```
- 適用於需要簡單函數的場合。
```python
# 使用 lambda 函數計算平方
square = lambda x: x ** 2
print(square(5)) # 輸出: 25
# 使用 lambda 函數實現加法
add = lambda a, b: a + b
print(add(3, 7)) # 輸出: 10
```
- 或是將函數當作參數輸入也很方便
```py
arr = [1, 2, 3, 4, 5]
arr2 = list(map(lambda x:x**2, arr))
print(arr2)
# 輸出:[1, 4, 9, 16, 25]
```
- **特殊參數傳入**:
- **\*args**
- `*args` 把「多出來的位置參數」打包成一個 **tuple**
- `args` 只是慣例命名,你也可以改成別的名字,但通常都用 `args`
```python
def my_sum(*args):
# args 是 tuple
total = 0
for x in args:
total += x
return total
print(my_sum(1, 2, 3)) # 輸出: 6
```
- **\*\*kwargs**
- `**kwargs` 會把「多出來的關鍵字參數」打包成一個 **dict**(字典)
- `kwargs` 也是慣例命名(keyword arguments)
```python
def show_profile(**kwargs):
# kwargs 是 dict
print(kwargs)
show_profile(name="Alice", age=20, city="Taichung")
# 輸出: {'name': 'Alice', 'age': 20, 'city': 'Taichung'}
# 變成一個字典
```
### 牛刀小試
1. 定義一個函數 `my_max(*args)`,可以接收任意多個數字並回傳最大值
2. 定義一個函數 `print_info(**kwargs)`,把所有資訊用「一行一個 key: value」印出來
例如呼叫:
```python
print_info(name = "Walter White", age = 52, job = "Math teacher")
```
輸出:
```
name: Walter White
age: 52
job: Math teacher
```
---
### 遞迴函數
- 遞迴(Recursion) 指的是「函數在定義中**呼叫自己**」
- 常用於可被拆解為「**規模更小、結構相同問題**」的情境
- 遞迴通常包含兩個部分:
- 終止條件(Base Case):什麼時候停止遞迴
- 遞迴呼叫(Recursive Case):問題如何縮小並再次呼叫自己
- 每個遞迴函數一定要有**終止條件**(base case),否則會造成無限呼叫,導致程式錯誤
- 範例:使用遞迴計算階乘(factorial)
利用 $n! = (n-1)! \times n$ 的觀念
$$
\begin{align}
n! &= 1 \times 2 \times 3 \times ... \times (n-1) \times n \\
&= (1 \times 2 \times 3 \times ... \times (n-1)) \times n \\
&= (n-1)! \times n
\end{align}
$$
```python
def fact(n):
if n == 0: # 終止條件
return 1
else:
return n * fact(n - 1) # 呼叫自己(問題縮小)
print(fact(3)) # 輸出: 6
```
- 遞迴函數的呼叫階層圖
```
fact(3)
│
├─ fact(2) * 3
│ │
│ ├─ fact(1) * 2
│ │ │
│ │ ├─ fact(0) * 1
│ │ │ │
│ │ │ └─ return 1
│ │ │
│ │ └─ return 1 * 1 = 1
│ │
│ └─ return 1 * 2 = 2
│
└─ return 2 * 3 = 6
```
### 牛刀小試
1. 定義一個函數 `fib(n)`,用遞迴的方式計算費氏數列,即
$$
\begin{equation}
F_n =
\begin{cases}
0, & n = 0 \\[6pt]
1, & n = 1 \\[6pt]
F_{n-1} + F_{n-2}, & n \ge 2
\end{cases}
\end{equation}
$$
---
## 單元 9:載入外部函數庫(Import)
- python有許多別人已經做好的函數庫,裡面包含不只函數,也可能有定義好的常數可供使用
例如:
- 數學計算 → `math`
- 隨機抽樣 → `random`
- 時間處理 → `time`, `datetime`
- 科學計算 → `numpy`
- 資料分析 → `pandas`
- **from import as**
- 利用`import 函數庫名稱` 就可以直接將函數庫導入,而函數庫就像一個資料夾,利用`.`來進入下一個資料夾或找到資料夾裡的函數
```python
import random # 導入函數庫
arr = [1, 3, 5, 7, 9, 11]
# random函數庫中的幾個函數
a = random.random() # # 隨機產生[0, 1)中的一個浮點數
b = random.randint(1, 10) # 隨機產生[1, 10]中的一個整數
c = random.choice(arr) # 隨機取得arr中的一個元素
print(a, b, c)
random.shuffle(arr) # 將arr內容打亂
print(arr)
```
random函數庫的詳細說明文件: [點這裡](https://docs.python.org/zh-tw/3.13/library/random.html)
- 如果只要用特定的幾個函數,可以用`from 函數庫 import 內容`的形式
避免載入太多用不到的函數
```python
from random import randint, random # 可用逗號區隔多個函數、類別
print(random())
print(randint(1, 10)) # 就不用加random.了
```
- `as` 可以幫模組取「簡短好打的名字」
```python
import math as m
print(m.sqrt(25))
print(m.pi)
```
### 牛刀小試
1. 搜尋網路,利用`math`函數庫,計算`1234567890`與`9876543210`的最大公因數(GCD)
2. 搜尋網路,利用`datetime`函數庫,印出當下日期與時間
---
## 單元 10:類別(class)
### 為什麼要有class?
- 想像今天如果我要儲存四個學生的名字、年齡、身高、體重,有什麼方法?
- **方法1**
```python
nameA, nameB, nameC, nameD = "Mike", "Dustin", "Lucas", "Will"
ageA, ageB, ageC, ageD = 13, 12, 12, 13
heightA, heightB, heightC, heightD = 131, 129, 134, 127
weightA, weightB, weightC, weightD = 47, 53, 42, 38
```
超多變數,非常雜亂
所以聰明的你想到了方法2,使用**陣列**儲存
- **方法2**
```python
name = ["Mike", "Dustin", "Lucas", "Will"]
age = [13, 12, 12, 13]
height = [131, 129, 134, 127]
weight = [47, 53, 42, 38]
```
但這樣同一個學生的資訊分散在四個陣列,難以看出關係
聰明的你又想到了方法3,可以用**字典**儲存
- **方法3**
```python
A = {"name": "Mike", "age": 13, "height": 131, "weight": 47}
B = {"name": "Dustin", "age": 12, "height": 129, "weight": 53}
C = {"name": "Lucas", "age": 12, "height": 134, "weight": 42}
D = {"name": "Will", "age": 13, "height": 127, "weight": 38}
```
不過這樣每次都要定義一個結構一模一樣的字典結構,會不會太麻煩?
---
### class定義
- 將**結構定義**跟**資料**分開,先做好模板,再做出成品
```python
class Student:
def __init__(self, name, age, height, weight):
self.name = name
self.age = age
self.height = height
self.weight = weight
```
- class 是一個模板,稱為**類別**
定義一個學生有`name`, `age`, `height`, `weight` 四種**屬性**
- 而實際應用這個模板所產生的物件,稱為**實例(instance)**
```python!
A = Student("Mike", 13, 131, 47)
B = Student("Dustin", 12, 129, 53)
C = Student("Lucas", 12, 134, 42)
D = Student("Will", 13, 127, 38)
```
---
### class 基本語法
- **最基本的 class 定義**
```python
class 類別名稱:
類別內容
```
- 類別名稱慣例使用 **大寫開頭**
- `Student` ✅
- `student` ❌
- **`__init__`:初始化函數(建構子)**
- 當你「建立一個物件」時,`__init__` 會自動被呼叫
- 用來設定這個物件一開始該有哪些屬性
```python
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
```
- `__init__` 不是自己呼叫的
而是在建立實例時自動執行:
```python
s = Student("Mike", 13) # 呼叫 __init__() 函數並帶入參數 "Mike", 13
```
- **存取物件的屬性**
```python
print(s.name) # 輸出: Mike
print(s.age) # 輸出: 13
```
- **修改屬性**
```python
s.age = 14
print(s.age) # 輸出: 14
```
### 方法(methods)
- 方法就是「寫在 class 裡的函數」
- 第一個參數一定是 `self`
```python!
class Student:
def __init__(self, name):
self.name = name
# 定義一個方法,使Student可以說話
def say(self, content):
print(f"{self.name}: {content}")
```
- 呼叫**方法**與呼叫函數很像,都要加上括號: `(參數)`
```python
s = Student("Will")
s.say("How are you?")
# 輸出:"Will: How are you?"
```
- **什麼是 `self`?**
- `self` 代表「**目前這個物件自己**」
- 用來在class定義中,找到呼叫這個方法的物件**本身**
- 呼叫方法時,Python會自動幫你帶入這個物件,傳入self
```python
s.say("How are you?")
↓
s.say(s, "How are you?") # 實際上Python會幫你這樣寫
↓
def say(self -> s, content -> "How are you?"): # 帶進函數之後
print(f"{s.name}: {"How are you?"}")
```
### 牛刀小試
1. 請定義一個 `Dog` 類別,並完成以下需求:
- `Dog` 有兩個屬性:
- `name`
- `age`
- 在 `__init__` 中設定這兩個屬性
- 定義一個方法 `bark`,呼叫後會印出:
```
<狗的名字>: Woof!
```
**範例使用方式**
```python
d = Dog("Buddy", 3)
d.bark()
```
**預期輸出**
```
Buddy: Woof!
```
2. 請延續你剛剛寫的 `Dog` 類別,加入一個方法 `grow_up`:
- `grow_up` 被呼叫時,讓狗的年齡加 1
- 並印出目前年齡
**範例使用方式**
```python
d = Dog("Buddy", 3)
d.grow_up()
d.grow_up()
```
**預期輸出**
```
Buddy is now 4 years old
Buddy is now 5 years old
```
---
### 繼承(Inheritance)
- **為什麼要有繼承?**
```python!
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
class Teacher:
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
```
- `Student`, `Teacher` 有重複的屬性 `name` 和 `age`,因為他們都是**人**
- 所以有更結構化的寫法
```python!
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super().__init__(name, age)
self.score = score
class Teacher(Person):
def __init__(self, name, age, salary):
super().__init__(name, age)
self.salary = salary
```
- `Student`和`Teacher`都會**繼承**`Person`的**屬性**以及**方法**
- 在這裡 `Student`和`Teacher` 稱為**子類別**,`Person` 為**父類別**
- 雖然 `Student`和`Teacher` 中沒有寫 `self.name`,但因為繼承,所以也有這個屬性
```python!
t = Teacher("Clarke", 49, 50000) # 對應 name, age, salary
print(t.name) # 輸出:Clarke
```
- **什麼是 `super()`?**
- `super()` 讓你可以找到「**父類別**」,尤其是被**覆蓋**掉的方法
```python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hi(self):
print(f"Hi, I'm {self.name}")
class Student(Person):
def __init__(self, name, age, score):
super().__init__(name, age)
self.score = score
def say_hi(self):
print("I am a student")
def super_say_hi(self):
super().say_hi()
s = Student("Mike", 11, 'A')
# (因為Student覆蓋掉了Person定義的say_hi)
s.say_hi() # 輸出:"I am a student"
s.super_say_hi() # 輸出:"Hi, I'm Mike"
```
### **牛刀小試**
請完成以下類別設計:
- 建立一個父類別 `Vehicle`
- 具有兩個屬性:
- `color`
- `tire`(輪胎數)
- 定義一個方法 `describe()`,用來描述交通工具的基本資訊
- 建立以下子類別,**皆需繼承 `Vehicle`**
- `Bike`
- 額外屬性:`electric`(`bool`,是否為電動)
- **輪胎數固定為 2(不能由外部傳入)**
- `Car`
- 額外屬性:`seats`
- **輪胎數固定為 4**
- `Ship`
- 額外屬性:`capacity`
- **輪胎數固定為 0**
- **方法要求**
- 父類別 `Vehicle` 定義 `describe()` 方法
- 每個子類別都要 **覆寫 `describe()`**
- 子類別的 `describe()` **必須使用 `super()` 呼叫父類別的 `describe()`**
- **範例程式**
```python
class Vehicle:
# 定義 __init__
def describe(self):
print(f"Color: {self.color}, Tires: {self.tire}")
class Bike(Vehicle):
def __init__(self, color, electric):
# 呼叫父類別 __init__,輪胎數固定為 2
def describe(self):
# 先呼叫父類別 describe()
# 再印出是否為電動腳踏車
class Car(Vehicle):
def __init__(self, color, seats):
# 呼叫父類別 __init__,輪胎數固定為 4
def describe(self):
# 先呼叫父類別 describe()
# 再印出座位數
class Ship(Vehicle):
def __init__(self, color, capacity):
# 呼叫父類別 __init__,輪胎數固定為 0
def describe(self):
# 先呼叫父類別 describe()
# 再印出容量
b = Bike("red", electric=True)
c = Car("black", seats=5)
s = Ship("white", capacity=3000)
b.describe()
c.describe()
s.describe()
```
---
### 預期輸出
```
Color: red, Tires: 2
This is an electric bike
Color: black, Tires: 4
This car has 5 seats
Color: white, Tires: 0
This ship can carry 3000 tons
```