> **GESP等级：三级 | 考级考点：GESP Python 三级**

# 第04节教案 —— 元组tuple：只读列表（60分钟）

> **GESP 考点**：#22 元组的创建、索引、切片、不可变特性、解包
> **教学形式**：坐标导入（0-10min）→ 知识精讲：创建/索引/切片/不可变/解包（10-30min）→ 演示 goto+解包（30-40min）→ 上机实操（40-55min）→ 小测收尾（55-60min）
> **时长**：60 分钟 | **对应文件**：`05_第04节_元组入门.py`、`课堂练习册_第04节_元组tuple.md`、`课后练习卡_第04节_元组tuple.md`
> **核心定位**：元组是 Python 四种集合类型中最特殊的一个——"不能改的列表"。孩子必须建立"元组=只读列表"的身体直觉，并通过 turtle 坐标场景深刻理解元组的不可变特性。

---

## GESP 知识映射

| GESP-1 考点 | 本关对应 | 身体锚点 |
|------------|---------|---------|
| #22 元组 | 创建 `()`、索引 `[0]`、切片 `[1:3]`、不可变、解包 `x,y=(100,200)` | 🛡️ 元组=只读列表，锁住了不能改 |

---

## 教学目标

孩子下课后能：

- **用自己的话解释元组**："元组 = 只读列表，不能添加、不能删除、不能修改"
- **创建各种元组**：`()` 空元组、`(1,2,3)` 多元素、`(5,)` 单元素（逗号不能丢）
- **用索引和切片访问元组**：`pos[0]` 取 x 坐标、`nums[1:4]` 切片
- **用解包一次性赋值**：`x, y = (100, 200)` → x=100, y=200
- **在 turtle 中用元组控制坐标**：`t.goto((100, 200))` 或解包后 `t.goto(x, y)`
- **回答小测题**：元组和列表最大的区别？——答：列表可变，元组不可变

---

## 教学节奏总览

| 时间段 | 时长 | 阶段 | 核心内容 | 身体锚点 | 关键记忆句 |
|--------|------|------|---------|---------|-----------|
| 0-10min | 10min | 导入——坐标就是元组 | `(100,200)` 是元组→"元组=不能改的列表"，对比列表 | 双手合拢锁住（不能改的手势） | "元组=只读列表，不能增删改" |
| 10-30min | 20min | 知识点精讲 | ①空元组`()` ②索引`[0]` ③切片`[1:3]` ④不可变 ⑤解包`x,y=(100,200)` | 解包：拆礼盒动作 | "解包=元组拆开赋给多个变量" |
| 30-40min | 10min | 演示——goto+解包 | `pos=(100,200); t.goto(pos); x,y=pos→解包` | 全班跟随指读 | "x,y=pos 一次拆开两个值" |
| 40-55min | 15min | 上机实操 | ①创建位置元组 ②索引取值 ③解包赋值 ④尝试修改元组→TypeError | 上机巡视+个别指导 | "元组不能改—改了报TypeError" |
| 55-60min | 5min | 小测收尾 | "元组和列表最大的区别？" + 全班齐读口诀 | 口答三问 | "元组=只读列表，不能增删改" |

> **节奏设计逻辑**：生活导入（坐标）→ 新知精讲（五种特性）→ 演示验证（turtle 实战）→ 上机实操（四关挑战）→ 小测巩固。

---

## 课前准备

### 讲台道具清单
- **一个玩具锁**（或一把挂锁）——演示元组的"不可变"：锁上了不能改
- **一个空礼盒**——演示"解包"：从礼盒中取出多个礼物分别递给不同的人
- **黑板/白板布局**：
  - 正中顶部：**🛡️ 第04节：元组 tuple——只读列表**
  - 左侧：创建区（`()` `(1,2)` `(5,)`）
  - 中央：索引/切片区（`pos[0]` `nums[1:4]`）
  - 右侧：解包区（`x,y=(100,200)`）+ 口诀区

### 学生电脑环境（课前确认）
- 每台电脑已打开 `05_第04节_元组入门.py`
- Python 环境正常，`turtle` 模块可用

---

## 阶段一：导入——坐标就是元组（0-10 分钟）

### 1.1 旧知回顾——复习列表（0-3 分钟）

> "同学们，上一关我们学了列表——还记得列表的三个操作吗？"

板书快速回顾：
```
列表 list = [1, 2, 3]
  ✅ 可以添加：list.append(4)
  ✅ 可以删除：list.remove(2)
  ✅ 可以修改：list[0] = 100
```

> "列表像一张可以随便改写的购物清单——加东西、划掉、改内容都行。"

### 1.2 坐标导入——(100, 200) 就是一个元组（3-7 分钟）

老师在黑板上画一个直角坐标系，标出点 `(100, 200)`：

> "在 Python 里，坐标 (100, 200) 是什么数据类型？其实它就叫——**元组**！"
> 
> 板书：`(100, 200)` → 这就是一个**元组 (tuple)**

**对比提问**：

> "如果列表是'可以改的清单'——元组是什么？"
> 
> 引导回答："不能改的……"
> 
> "对！元组 = **只读列表**——只能看，不能改！"

**身体锚点——锁住手势**：

全班起立，双手交叉握拳（像锁住一样）：

> "看到没有？这个手势 = 锁住了！元组跟列表一样可以索引、切片——但不能添加、不能删除、不能修改！"
>
> 齐喊三遍：**"元组 = 只读列表，不能增删改！"**

### 1.3 为什么需要元组？（7-10 分钟）

> "你们可能会问——既然列表什么都能做，为什么还要元组？"
> 
> "想象一下——你的身份证号 `(110101, 2005, 3, 15)`，它能改吗？"
> 
> 全班："不能！"
> 
> "对！有些数据天生就不能改——坐标、日期、颜色值——改了就错了！"
> 
> "元组帮我们'锁住'这些数据——防止不小心改错。Python 里有一句名言——**'不可变的数据更安全'**。"

**生活例子引导**（快速互动）：

| 场景 | 用列表还是元组？ | 为什么？ |
|:----:|:--------------:|:-------:|
| 超市购物清单 | 列表 ✅ | 随时加减物品 |
| 身份证上的出生年月 | 元组 ✅ | 不能改！ |
| 游戏背包里的道具 | 列表 ✅ | 捡起/丢弃/使用 |
| 地图上的坐标点 | 元组 ✅ | 坐标不变，防止误改 |

**关键记忆句**（全班齐读 2 遍）：

> **"元组 = 只读列表，不能增删改！"**

---

## 阶段二：知识点精讲——元组的五种特性（10-30 分钟）

### 2.1 创建空元组 `()` 和多元素元组（10-13 分钟）

**第一步：空元组**（2 分钟）

老师拿起空礼盒：

> "空元组——就是一对小括号 `()`，里面什么都没有。"

```
empty = ()        # 空元组
print(empty)      # ()
print(type(empty))  # <class 'tuple'>
```

> "空元组有什么用？——有时候你不知道数据，先占个位置，后面再决定。"

**第二步：多元素元组**（3 分钟）

```
pos = (100, 200)       # 2 个元素——坐标
nums = (10, 20, 30)    # 3 个元素——数字序列
mixed = (1, "hello", True)  # 混合类型——字符串/布尔也能放！
```

板书对比列表和元组的创建：

```
列表 list：  numbers = [10, 20, 30]    ← 方括号 []
元组 tuple： numbers = (10, 20, 30)    ← 圆括号 ()
```

> "区别就在符号——列表是方括号 `[]`，元组是圆括号 `()`。一个能改，一个不能改。"

**第三步：单元素元组——逗号不能丢！**（3 分钟）

老师在黑板上写：

```
single = (5)       # → 这是数字 5，不是元组！
single = (5,)      # → 这才是元组！(5,)
```

> "注意！如果只有一个元素——**后面必须加逗号！** 不加逗号就是普通数字被括号括起来。"

```
✅ (5,)      → 元组，里面是 5
❌ (5)       → 就是数字 5，不是元组
✅ ("hi",)  → 元组，里面是 "hi"
❌ ("hi")   → 就是字符串 "hi"，不是元组
```

**身体锚点——逗号手势**：

老师伸出食指："一个元素的时候——**手指（元素）后面要加一个逗号点头**！"

全班跟着做："`(5,)`——数字 5 后面逗号一点——才是元组！不点逗号(`(5)`)——就是数字！"

**关键记忆句**（齐读 2 遍）：

> **"单元素元组——数字后面加逗号！`(5,)` 是元组，`(5)` 就是数字！"**

**核对理解**（快速点名 3 人）：
- "空元组怎么写？" → "`()`"
- "`(3)` 是元组吗？" → "不是！没有逗号，是数字 3"
- "`(3,)` 是元组吗？" → "是！有逗号"

### 2.2 索引取值——和列表一模一样（13-16 分钟）

> "元组的索引和列表完全一样——从 0 开始数。"

板书：
```
pos = (100, 200)
       ↑       ↑
     pos[0]  pos[1]
     
pos[0] → 100    (x 坐标)
pos[1] → 200    (y 坐标)
```

> "`pos[0]` 读作 'pos 的 0 号元素'——就是 100。`pos[1]` 就是 200。"

**举例——用索引控制乌龟移动**：

```
pos = (100, 200)
t.goto(pos[0], pos[1])    # 等价于 t.goto(100, 200)
```

> "看到了吗？以前我们写 `t.goto(100, 200)`——现在用元组 `pos`，用 `pos[0]` 和 `pos[1]` 取出值。好处是：坐标存在一个变量里，后面改坐标只用改 `pos` 一个地方！"

**负数索引**：

```
pos = (100, 200)
pos[-1] → 200    # 倒数第一个
pos[-2] → 100    # 倒数第二个
```

**板书对比——列表 vs 元组的索引**：

```
列表：  my_list = [10, 20, 30]
         my_list[0] → 10 ✅  可以改：my_list[0] = 99
         
元组：  my_tuple = (10, 20, 30)
         my_tuple[0] → 10 ✅  不能改：my_tuple[0] = 99 ❌
```

**关键记忆句**：

> **"索引取值：元组和列表一样！区别只有：一个能改，一个不能！"**

### 2.3 切片——和列表一样（16-19 分钟）

> "切片也跟列表一样——`[start:end]`——取从 start 到 end-1 的元素。"

```
nums = (10, 20, 30, 40, 50)

nums[1:4]  → (20, 30, 40)    # 从第1个到第3个（到第4个前停）
nums[:3]   → (10, 20, 30)    # 从头到第2个（到第3个前停）
nums[2:]   → (30, 40, 50)    # 从第2个到最后
nums[-3:]  → (30, 40, 50)    # 最后3个
nums[:]    → (10, 20, 30, 40, 50)  # 复制整个元组
```

> "切片结果**还是元组**——不是列表！因为元组的切片产生新元组。"

**快速练习**（写在草稿纸上，30 秒）：

```
colors = ("red", "green", "blue", "yellow", "purple")
① colors[1:3]  → ?
② colors[:2]   → ?
③ colors[-2:]  → ?
```

答案：① `("green", "blue")` ② `("red", "green")` ③ `("yellow", "purple")`

### 2.4 不可变特性——不能改！（19-24 分钟）

> "这个是最重要的——元组的灵魂：**不可变** = 不能修改。"

老师拿起锁，"咔嚓"锁上：

> "元组就像被锁住了——不能添加、不能删除、不能修改。"

**三个"不能"板书**：

```
pos = (100, 200)

❌ 不能修改：pos[0] = 300     → TypeError
❌ 不能添加：pos.append(300)  → AttributeError
❌ 不能删除：del pos[0]       → TypeError
                 pos.remove(100) → AttributeError
```

**对比实验**（投影演示）：

```python
# 列表——可以改
my_list = [1, 2, 3]
my_list[0] = 100    # ✅ 改成 [100, 2, 3]
my_list.append(4)   # ✅ 变成 [100, 2, 3, 4]

# 元组——不能改
my_tuple = (1, 2, 3)
my_tuple[0] = 100   # ❌ TypeError！
```

> "看到了吗？一模一样的操作——列表可以，元组报错！"

**互动提问**：

> "谁来告诉我——如果我非想改元组里的一个元素，怎么办？"

引导回答："把元组转成列表 → 修改 → 再转回元组！"

```
temp = list(my_tuple)    # (1,2,3) → [1,2,3]
temp[0] = 100            # 改
my_tuple = tuple(temp)   # [100,2,3] → (100,2,3)
```

> "但是——这样会多写三行代码。Python 设计者就是故意让你觉得'麻烦'——提醒你：**这个数据本来就不该改！**"

**关键记忆句**（齐读 3 遍）：

> **"元组不能改——改了报 TypeError！"**

### 2.5 元组解包——拆开赋给多个变量（24-30 分钟）

老师拿起空礼盒（里面放两张纸条，分别写 100 和 200）：

> "元组解包 = 拆礼盒！礼盒里有多个礼物——拿出来分别递给不同的人。"

**板书**：

```
pos = (100, 200)
x, y = pos    # 拆开！x 拿 100，y 拿 200
# → x = 100, y = 200
```

> "左边写多个变量，逗号隔开。右边是元组。Python 自动把元组的第 0 个给 x，第 1 个给 y——一次搞定！"

**推演过程**：

```
pos = (100, 200)
x, y = pos
↓↓↓
x = 100    # pos[0] 给 x
y = 200    # pos[1] 给 y
```

**解包的三种经典用法**（逐步展示）：

**用法 1：坐标解包 + goto**

```python
pos = (100, 200)
x, y = pos             # 拆开
t.goto(x, y)           # 用变量移动
```

> "原来要写 `t.goto(pos[0], pos[1])`——用了解包，`t.goto(x, y)` 清爽多了！"

**用法 2：一次性给多个变量赋值**

```python
a, b, c = (1, 2, 3)
# a=1, b=2, c=3
```

> "一行代码给三个变量赋值！不用写三行 `a=1`、`b=2`、`c=3`。"

**用法 3：交换两个变量——Python 独门绝技！**

```python
x = 5
y = 10
x, y = y, x    # 交换！
# x=10, y=5
```

> "其他语言要写三行加一个临时变量——Python 一行搞定！右边 `(y, x)` 就是一个元组 `(10, 5)`，然后解包给 `x, y`。"

**互动演练**（全班齐答）：

> "看黑板——`name, age = ("小明", 12)`——name 等于？"
> 
> 全班："小明！"
> 
> "age 等于？"
> 
> 全班："12！"

**身体锚点——拆礼盒手势**：

老师双手做出打开礼盒的动作：

> "元组 `(100, 200)` 像关着的礼盒——解包 `x, y = pos` 像打开礼盒——拿出两张纸条分别递给 x 和 y。"

全班跟着做：双手合拢（元组）→ 打开（解包）→ 左手伸（x=100）→ 右手伸（y=200）

**关键记忆句**（齐读 3 遍）：

> **"解包 = 元组拆开赋给多个变量！"**
> **"x, y = pos 一行等于两行！"**

**核对理解**（快速点名 3 人）：
- "`a, b = (10, 20)` → a=?, b=?" → "a=10, b=20"
- "解包的好处是什么？" → "不用写索引，直接拿值"
- "`x, y = y, x` 做了什么？" → "交换了两个变量的值！"

---

## 阶段三：演示——goto + 解包（30-40 分钟）

### 3.1 全班指读代码（30-33 分钟）

打开 `05_第04节_元组入门.py`，投影模式 5 的代码：

```python
# 模式5：元组解包——拆开赋给多个变量
pos = (200, -100)
x, y = pos                    # 一次拆开！
print("x =", x, ", y =", y)
t.goto(x, y)
```

**全班伸出右手食指，跟老师一起逐行指读**：

> "`pos = (200, -100)` —— 创建一个坐标元组！"
> "`x, y = pos` —— 解包！x 拿 x 坐标 200，y 拿 y 坐标 -100！"
> "`print("x =", x, ", y =", y)` —— 打印出来验证！"
> "`t.goto(x, y)` —— 用解包后的变量移动乌龟！"

**对比提问**：

> "不用解包怎么写？"
> 
> "`t.goto(pos[0], pos[1])`"
> 
> "用了解包呢？"
> 
> "`x, y = pos` → `t.goto(x, y)`——更简单、更清楚！"

### 3.2 元组作为 goto 参数直接传入（33-35 分钟）

> "Python 还有一个更厉害的特性——`t.goto()` 可以直接接收元组！"

```python
pos = (100, 200)
t.goto(pos)         # ✅ 直接传元组！
# 等价于 t.goto(100, 200)
```

> "不需要解包！不需要索引！直接把元组扔进去——Python 自动拆开用！"

**全班操作**（现场投影演示）：

1. `pos = (-100, 150)` → 创建元组
2. `t.goto(pos)` → 直接传入！乌龟移动到了 (-100, 150)
3. `pos2 = (200, -50)` → 再创建一个
4. `t.goto(pos2)` → 又移动了！

> "看到了吗？元组和 turtle 是天生一对——坐标天然就是元组！"

### 3.3 元组存多个坐标，循环遍历（35-38 分钟）

```python
# 元组列表 = 多个坐标点
points = [(-100, 0), (0, 100), (100, 0), (0, -100)]

for point in points:
    t.goto(point)        # 直接传入元组！
    # 或者解包：
    # x, y = point
    # t.goto(x, y)
```

**投影运行效果**：乌龟依次跳到四个点，画出一个菱形。

> "看！`points` 是一个**列表**，里面每个元素是**元组**——列表+元组组合使用！列表负责'多个'，元组负责'坐标'——各司其职！"

### 3.4 不可变验证——现场试错（38-40 分钟）

> "现在——我们试一下改元组，看看会发生什么。"

老师在投影上输入：

```python
pos = (100, 200)
pos[0] = 300    # 尝试修改
```

→ 瞬间报错：`TypeError: 'tuple' object does not support item assignment`

> "来！全班一起读这个报错——"
> 
> 全班："TypeError——'tuple' 对象不支持元素赋值！"
> 
> "这就是 Python 在告诉你——**元组不能改！** 它用报错保护了你的数据安全。"

**关键对比**（板书）：

```
列表改值：my_list[0] = 99    ✅ 正常执行
元组改值：my_tuple[0] = 99  ❌ TypeError 报错
```

> "这就是列表和元组**最大的区别**——记住了，等下小测要考！"

---

## 阶段四：上机实操——四道题（40-55 分钟）

> "现在轮到你们亲自动手了！四关挑战——从⭐到⭐⭐⭐，难度递增。完成一关举手，我来看。"

### 实操①：创建位置元组（40-43 分钟 · ⭐）

**题目**：补全下面的代码，用元组存 4 个坐标点，画一个矩形。

```python
# 实操①：创建位置元组——画矩形
import turtle
t = turtle.Turtle()
t.speed(6)

# 补全代码——创建 4 个位置元组
p1 = (____, ____)     # ① 左下角（-100, -50）
p2 = (____, ____)     # ② 右下角（100, -50）
p3 = (____, ____)     # ③ 右上角（100, 50）
p4 = (____, ____)     # ④ 左上角（-100, 50）

t.penup()
t.goto(p1)
t.pendown()
t.goto(p2)
t.goto(p3)
t.goto(p4)
t.goto(p1)            # 回到起点，闭合矩形

turtle.done()
```

**答案**：
- ① `-100, -50`
- ② `100, -50`
- ③ `100, 50`
- ④ `-100, 50`

**引导话术**：

> "矩形有四个顶点——每个顶点是一个坐标元组。从**左下角开始**，顺时针走一圈——右下→右上→左上→回到左下。"

**巡视重点**：

| 检查项目 | 通过标准 |
|---------|---------|
| 元组用圆括号 | `(...)` 不是 `[...]` |
| 坐标值正确 | p1=(-100,-50), p2=(100,-50), p3=(100,50), p4=(-100,50) |
| goto 直接传元组 | `t.goto(p1)`——Python 自动解包 |
| 画闭合矩形 | 最后 `t.goto(p1)` 回到起点 |

**能力分层**：
- 完成的孩子：改成画三角形（3 个顶点）、五边形（5 个顶点）
- 未完成的孩子：先检查元组括号，再看坐标正负号

### 实操②：索引取值——画折线（43-46 分钟 · ⭐）

**题目**：用元组存坐标序列，用 `for` 循环 + 索引取值画折线。

```python
# 实操②：索引取值——画折线
import turtle
t = turtle.Turtle()
t.speed(6)
t.color("orange")

# 元组列表：4 个坐标点
points = ((-200, 100), (-50, 150), (100, 50), (200, 120))

t.penup()
t.goto(____)              # ① 跳到第一个点——用 points[0]

t.pendown()
# 用 for 循环逐个连接其余点
for i in range(1, len(points)):
    t.goto(____)          # ② 用索引取值——points[i]（i 从1开始）

turtle.done()
```

**答案**：
- ① `points[0]`
- ② `points[i]`

**引导话术**：

> "`points` 是一个**元组**——里面存了 4 个坐标元组。第一个点用 `points[0]`，后面用循环依次取 `points[1]`、`points[2]`、`points[3]`。索引从 0 开始——跟列表一样！"

**巡视重点**：

| 检查项目 | 通过标准 |
|---------|---------|
| 跳到第一个点 | `t.goto(points[0])` |
| 循环范围 | `range(1, len(points))`——从 1 到 3 |
| 索引取值 | `points[i]`——不是直接 `points` |
| 折线效果 | 一条连续的折线穿过 4 个点 |

**能力分层**：
- 完成的孩子：增加坐标点数量（6 个/8 个），观察折线变化
- 挑战：用 `for point in points:` 直接遍历（不用索引）

### 实操③：解包赋值——画星形（46-50 分钟 · ⭐⭐）

**题目**：用元组存五角星的 5 个顶点坐标，用解包读取每个坐标画星形。

```python
# 实操③：解包赋值——画五角星
import turtle
t = turtle.Turtle()
t.speed(7)
t.color("red")

# 5 个星形顶点坐标
star_points = [
    (0, 100),
    (-95, 30),
    (-59, -81),
    (59, -81),
    (95, 30)
]

# 用解包读取第一个顶点
____, ____ = star_points[0]    # ① 解包第一个点
t.penup()
t.goto(x0, y0)
t.pendown()

# 用解包画五角星（隔点连线）
for i in range(1, 6):
    x, y = _____________       # ② 解包第 i 个顶点（取模5回到起点）
    t.goto(x, y)

turtle.done()
```

**答案**：
- ① `x0, y0`
- ② `star_points[i % 5]`

**引导话术**：

> "五角星的顶点坐标已经帮你们算好了——5 个点。用解包 `x, y = star_points[i]` 拿到坐标——不用写 `star_points[i][0]` 和 `star_points[i][1]`——解包一步到位！"

**关键提示**——`i % 5`：

> "五角星是隔点连线——0→2→4→1→3→0。但我们这里用 `i % 5` 取模——当 i=5 时 `5 % 5 = 0`，回到起点，星形闭合。"

**巡视重点**：

| 检查项目 | 通过标准 |
|---------|---------|
| 解包第一个点 | `x0, y0 = star_points[0]` |
| 循环中用解包 | `x, y = star_points[i % 5]` |
| 取模闭合 | `i % 5` 确保 i=5 时取到 0（起点） |
| 星形效果 | 5 条线，首尾相连，形成五角星 |

**能力分层**：
- 完成的孩子：画六角星（6 个顶点，隔 1 点连线）
- 加颜色：`colors = ["red","orange","yellow","green","blue","purple"]` 每边不同色

### 实操④：尝试修改元组 → TypeError（50-55 分钟 · ⭐⭐）

**题目**：写代码验证——元组真的不能修改，然后展示修改成功的对比（用列表）。

```python
# 实操④：尝试修改元组 → TypeError
import turtle
t = turtle.Turtle()
t.speed(6)

# 第一步：尝试修改元组
my_tuple = (10, 20, 30)

print("my_tuple =", my_tuple)
print("尝试修改：my_tuple[0] = 100...")

# 补全下面这行——尝试修改元组第一个元素
___________          # ① my_tuple[0] = 100

print("✅ 竟然成功了？——不可能！会报TypeError！")
print()

# 第二步：对比——列表可以修改
my_list = [10, 20, 30]
print("my_list =", my_list)
print("修改：my_list[0] = 100...")

___________          # ② 修改列表第一个元素
print("my_list =", my_list)
print("✅ 列表修改成功！")
print()

# 第三步：输出结论
print("=" * 40)
print("结论：")
print("列表 list → 可以修改 ✅")
print("元组 tuple → 不能修改 ❌ → 会报 TypeError")
print("=" * 40)

t.write("元组不能改！改了报TypeError", font=("Arial", 14, "bold"))

turtle.done()
```

**答案**：
- ① `my_tuple[0] = 100`
- ② `my_list[0] = 100`

**引导话术**：

> "先试元组——`my_tuple[0] = 100`——你觉得会怎样？"
> 
> "报了 TypeError！Python 用红色报错保护了元组！"
>
> "再试列表——`my_list[0] = 100`——成功修改！"
>
> "这就是列表和元组最大的区别——**列表可变，元组不可变！**"

**注意**：实操④会报错中断——**在运行前先让学生知道会报错**，或者让学生先注释掉第①行，运行一次看正常输出，再取消注释看报错。

> "来，先注释掉第①行——运行一次看看正常输出。然后取消注释——再运行一次——看看 Python 怎么'生气'的！"

**能力分层**：
- 完成的孩子：再试 `my_tuple.append(40)`、`del my_tuple[0]`——分别看不同的报错
- 挑战：`temp = list(my_tuple)` 转列表 → 修改 → `my_tuple = tuple(temp)` 转回元组——"绕路修改法"

---

## 阶段五：小测 + 收尾（55-60 分钟）

### 5.1 小测：元组和列表最大的区别？（55-57 分钟）

**投影问题**：

```
┌─────────────────────────────────────────┐
│           📝 小测（全班口答）              │
│                                          │
│ 元组和列表最大的区别是什么？               │
│                                          │
│ ① 语法区别：元组用____，列表用____        │
│ ② 核心区别：元组____，列表____            │
│ ③ 判断：下面哪个操作会报 TypeError？      │
│    A. my_list[0] = 100                    │
│    B. my_tuple[0] = 100                   │
│ ④ 解包是什么意思？                       │
│    x, y = (100, 200) → x=___, y=___      │
└─────────────────────────────────────────┘
```

**答案**：
- ① 元组用 `()`，列表用 `[]`
- ② 元组**不可变**（不能改），列表**可变**（可以改）
- ③ **B** 会报 TypeError
- ④ x=100, y=200——解包 = 拆开元组赋值给多个变量

**追问**（能力排查）：
- "`(5)` 和 `(5,)` 哪个是元组？" → "`(5,)`——单元素元组必须加逗号！"
- "元组可以索引吗？" → "可以——`pos[0]` 取第一个元素"
- "元组可以切片吗？" → "可以——切片结果还是元组"

**易错点确认**：

> "注意这三个极容易混淆的点："
>
> ① **语法**：元组是 `()`，列表是 `[]`——千万别混了！
> ② **单元素**：`(5,)` 是元组，`(5)` 是数字——**逗号是关键！**
> ③ **不可变**：元组不能改——改了报 TypeError！

### 5.2 全班齐读——核心金句（57-59 分钟）

全体起立，老师领读一句，全班跟读一句（拍手打节奏）：

```
① 元组 = 只读列表，不能增删改！
② 单元素元组——数字后面加逗号！(5,) 是元组，(5) 就是数字！
③ 元组不能改——改了报 TypeError！
④ 解包 = 元组拆开赋给多个变量！
⑤ x, y = pos 一行等于两行！
⑥ 列表可变，元组不可变——最大区别！
```

**灵魂收尾三问**：

> **一问**："元组和列表最大的区别是什么？" → "**列表可变，元组不可变！**"
>
> **二问**："解包 `x, y = (100, 200)` 是什么意思？" → "**把元组拆开，100 给 x，200 给 y！**"
>
> **三问**（快速点名 3 人，每人 3 秒内答对即过关）：
> - "`(3,)` 是元组吗？" → "是！有逗号！"
> - "`my_tuple[0] = 99` 会怎样？" → "报 TypeError！"
> - "元组可以用 for 循环吗？" → "可以！元组也是序列，跟列表一样可以遍历！"

**过渡下一关**（59-60 分钟）：

> "今天你们学会了第五种数据类型——**元组 tuple**——'只读列表'。元组的不可变特性让数据更安全——坐标、日期、固定配置用元组存，不怕误改。"
>
> "下一关——**字典 dict**——'有标签的盒子'。列表用数字索引找元素，字典用**名字**（键）来找——更强大的数据组织方式！下课！"

**最后检查**：每人保存了代码 ✓ / 关掉了电脑 ✓

---

## 板书设计

```
┌──────────────────────────────────────────────────────────────┐
│        🛡️ 第04节：元组 tuple——只读列表                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─ 创建 ────────────────────────────────────────────┐      │
│  │  ()          空元组                                   │      │
│  │  (100, 200)  坐标元组                                 │      │
│  │  (5,)        单元素——逗号不能丢！                     │      │
│  └────────────────────────────────────────────────────┘      │
│                                                              │
│  ┌─ 索引/切片 ───────────────────────────────────────┐       │
│  │  pos[0] → 100    pos[-1] → 200                      │       │
│  │  nums[1:4] → (20,30,40) 切片结果还是元组             │       │
│  └────────────────────────────────────────────────────┘       │
│                                                              │
│  ┌─ 不可变 ──────────────────────────────────────────┐       │
│  │  my_tuple[0] = 99  ❌ TypeError                    │       │
│  │  my_tuple.append(4) ❌ AttributeError               │       │
│  │  del my_tuple[0]    ❌ TypeError                    │       │
│  │  ★ 元组不能添加/删除/修改！                         │       │
│  └────────────────────────────────────────────────────┘       │
│                                                              │
│  ┌─ 解包 ────────────────────────────────────────────┐       │
│  │  x, y = (100, 200)  →  x=100, y=200                │       │
│  │  x, y = y, x        →  交换两个变量！               │       │
│  │  ★ 解包 = 元组拆开赋给多个变量                      │       │
│  └────────────────────────────────────────────────────┘       │
│                                                              │
│  ┌─ 列表 vs 元组 ────────────────────────────────────┐       │
│  │  列表 = [1,2,3]  →  ✅ 可变                        │       │
│  │  元组 = (1,2,3)  →  ❌ 不可变                       │       │
│  │  ★ 最大区别：列表可变，元组不可变                    │       │
│  └────────────────────────────────────────────────────┘       │
│                                                              │
│  📝 小测：元组和列表最大的区别？→ 列表可变，元组不可变！      │
│                                                              │
│  记忆钩子："元组=只读列表，不能增删改"                        │
│            "解包=元组拆开赋给多个变量"                        │
└──────────────────────────────────────────────────────────────┘
```

---

## 易错点

| 易错点 | 错误表现 | 根因 | 课堂对策 |
|--------|---------|------|---------|
| 单元素元组忘逗号 | `(5)` 被当成数字 5，不是元组 | 不知道单元素元组必须加逗号 | 口诀："单元素元组——数字后面加逗号！`(5,)` 是元组，`(5)` 就是数字！" |
| 元组用方括号创建 | `[1, 2, 3]` 误当元组使用 | 混淆了列表 `[]` 和元组 `()` 的语法 | 板书对比：`list = [ ]` vs `tuple = ( )`，强调符号区别 |
| 尝试修改元组 | `my_tuple[0] = 100` 报 TypeError 后不知所措 | 不理解"不可变"的含义 | 现场演示报错，全班齐读报错信息，解释"Python 在用错误保护数据" |
| 解包时变量个数不匹配 | `a, b = (1, 2, 3)` 报 ValueError | 左边 2 个变量，右边 3 个值，无法解包 | 口诀："解包像分糖果——人数=糖果数，一一对应！多了/少了都会报错" |
| 用 `==` 代替 `=` 创建元组 | `pos == (100, 200)`（比较而非赋值） | 混淆赋值和比较运算符 | 强调：`=` 是赋值，`==` 是比较——创建元组用 `=` |
| 解包和索引混用 | `x, y = pos[0], pos[1]`（啰嗦） | 不知道可以直接 `x, y = pos` | "既然要拆开整个元组——为什么不一步到位？`x, y = pos` 更简单！" |
| 认为元组完全不能变 | 以为元组变量本身也不能变 | 混淆了"元组内容不可变"和"变量可以重新赋值" | 演示：`pos = (1,2); pos = (3,4)`——pos 变量可以指向新元组，但元组 (1,2) 本身不能改 |

---

## GESP 衔接

本关完整覆盖 GESP 一级考点 **#22 元组**，通过"创建→索引→切片→不可变→解包"逐层递进，孩子经历了从"认识元组"到"用元组控 turtle"再到"验证不可变"的三级认知跃迁。

**本关结束后孩子应能回答**：

1. 元组和列表最大的区别是什么？（答：列表可变，元组不可变）
2. 如何创建单元素元组？（答：`(5,)`——必须加逗号）
3. 元组可以索引和切片吗？（答：可以——和列表一样的操作）
4. 解包是什么意思？（答：把元组拆开，元素依次赋给多个变量）
5. 尝试修改元组会怎样？（答：报 TypeError——元组不可变）

**下一节预告**：**字典 dict**——"有标签的盒子"。列表用数字索引，字典用名字（键）来找数据——`student = {"name": "小明", "age": 12}`。

---

## 教员备忘

- **本关最关键 10 分钟**：0-10 分钟的导入——"坐标 (100, 200) 就是元组"和"元组=只读列表"的身体锚点。用锁的手势建立"不能改"的直觉。这个做好了，后面所有特性都围绕"不可变"展开。
- **最易混淆点**：`(5)` vs `(5,)`——单元素元组的逗号。几乎 80% 的孩子第一次写会忘逗号。策略：先预警+板书对比，纠错时全班齐读"单元素元组——数字后面加逗号！"
- **实操④会报错中断**：建议先让学生注释掉第①行运行一次（看正常输出），再取消注释运行一次（看 TypeError 报错）。让学生亲眼看到"报错本身就是验证"。
- **三层实操的时间分配**：实操①（3 分钟）- 实操②（3 分钟）- 实操③（4 分钟）- 实操④（5 分钟）。如果时间紧张，实操④可以简化为只写两行关键代码（修改元组+修改列表对比）。
- **能力分层**：完成全部四关的孩子进入"自由探索"——画多角星、用元组存路径点让乌龟沿路径行走、尝试转列表再修改。
- **投影准备**：提前在投影上打开 `05_第04节_元组入门.py`，模式 5（解包）和模式 4（不可变）预先框选好。
- **小测是快速口答**：不需要写下来。全班齐答→老师听答案→如果有分歧→立刻展开讨论，让元组"报错"展示不可变。

---

## 文件清单

```
05_数据类型/第04节_元组tuple/
├── 05_第04节_元组入门.py              ← 核心上机文件（本关）
├── 教案_第04节_元组tuple.md           ← 本文件
├── 课堂练习册_第04节_元组tuple.md     ← 课堂练习
├── 课后练习卡_第04节_元组tuple.md     ← 课后作业
└── （后续节次文件...）
```
