---
# System prepended metadata

title: Python 基礎/進階
tags: [講義]

---

# 從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
```