# GESP等级：三级 | GESP Python 三级考点

# ============================================
# 第4节：元组tuple——只读列表
#
# GESP #22 元组的创建、索引、切片、不可变特性、解包
# 核心口诀：
#   元组 = 只读列表，不能增删改
#   解包 = 元组拆开赋给多个变量
# ============================================
import turtle

t = turtle.Turtle()
t.shape("turtle")
t.speed(8)

# ============================================
# 模式1：认识元组——坐标就是元组
# ============================================
print("===== 模式1：认识元组 =====")
print("坐标 (100, 200) 就是一个元组！")
print("元组 = 不能修改的列表")
print()

# 创建一个元组
pos = (100, 200)
print("pos =", pos)
print("类型：", type(pos))  # <class 'tuple'>
print()

# 空元组
empty_tuple = ()
print("空元组：", empty_tuple)
print("类型：", type(empty_tuple))
print()

# 单元素元组（注意：必须加逗号！）
single = (5,)         # 正确——元组
not_tuple = (5)       # 错误——只是数字 5，不是元组！
print("(5,) 是元组：", type(single))
print("(5)  不是元组：", type(not_tuple))
print()

t.penup()
t.goto(-350, 250)
t.pendown()
t.color("blue")
t.write("元组 = (100, 200)  坐标就是元组！", font=("Arial", 12, "normal"))

# ============================================
# 模式2：索引取值——和列表一样
# ============================================
print("===== 模式2：索引取值 =====")
t.penup()
t.goto(-350, 200)
t.pendown()
t.color("darkblue")
t.write("索引取值: pos[0]=100  pos[1]=200", font=("Arial", 10, "normal"))

# 创建一个位置元组
pos = (150, 50)
print("pos =", pos)
print("pos[0] =", pos[0])  # 150 — 第0个元素（x坐标）
print("pos[1] =", pos[1])  # 50  — 第1个元素（y坐标）

# 用索引取值移动乌龟
t.penup()
t.goto(0, 0)
t.pendown()
t.color("green")
t.goto(pos[0], pos[1])     # 用元组的元素作为坐标
t.write("移动到 pos[0], pos[1]", font=("Arial", 10, "normal"))
print()

# 索引也能从后往前取（负数索引）
print("pos[-1] =", pos[-1])  # 50 — 最后一个元素
print("pos[-2] =", pos[-2])  # 150 — 倒数第二个元素

# ============================================
# 模式3：元组切片——和列表一样
# ============================================
print("===== 模式3：元组切片 =====")
t.penup()
t.goto(-350, 130)
t.pendown()
t.color("purple")
t.write("切片: (10,20,30,40,50)[1:4] = (20,30,40)", font=("Arial", 10, "normal"))

nums = (10, 20, 30, 40, 50)
print("nums =", nums)
print("nums[1:4] =", nums[1:4])    # (20, 30, 40)
print("nums[:3] =", nums[:3])      # (10, 20, 30)
print("nums[2:] =", nums[2:])      # (30, 40, 50)
print("nums[-3:] =", nums[-3:])    # (30, 40, 50)
print()

# ============================================
# 模式4：不可变特性——不能修改！
# ============================================
print("===== 模式4：不可变特性 =====")
t.penup()
t.goto(-350, 60)
t.pendown()
t.color("red")
t.write("不可变: 不能添加/删除/修改 → TypeError!", font=("Arial", 10, "normal"))

pos = (100, 200)
print("pos =", pos)
print("元组不能修改！试试看会报什么错？")
print()

# 下面这些操作会报 TypeError——演示前注释掉，避免程序中断
# pos[0] = 300          # ❌ TypeError: 'tuple' object does not support item assignment
# pos.append(300)       # ❌ AttributeError: 'tuple' object has no attribute 'append'
# pos.remove(100)       # ❌ AttributeError: 'tuple' object has no attribute 'remove'
# del pos[0]            # ❌ TypeError: 'tuple' object doesn't support item deletion

print("✅ 元组是只读列表——不能增、不能删、不能改！")
print()

# ============================================
# 模式5：元组解包——拆开赋给多个变量
# ============================================
print("===== 模式5：元组解包 =====")
t.penup()
t.goto(-350, -10)
t.pendown()
t.color("darkgreen")
t.write("解包: x, y = (100, 200)  →  x=100, y=200", font=("Arial", 10, "normal"))

# 解包赋值
x, y = (100, 200)
print("x, y = (100, 200)")
print("x =", x)   # 100
print("y =", y)   # 200
print()

# 元组解包 + goto 移动
pos = (200, -100)
x, y = pos                    # 一次拆开！
print("pos =", pos)
print("x, y = pos  →  x =", x, ", y =", y)
t.penup()
t.goto(0, -200)
t.pendown()
t.color("darkorange")
t.goto(x, y)                  # 解包后直接用变量
t.write("解包后 goto(x, y)", font=("Arial", 10, "normal"))
print()

# 多个变量一次赋值（元组解包的经典用法）
a, b, c = (1, 2, 3)
print("a, b, c = (1, 2, 3)")
print("a =", a, ", b =", b, ", c =", c)

# 交换两个变量——元组解包的魔法！
x = 5
y = 10
print("交换前：x =", x, ", y =", y)
x, y = y, x          # 右边 (y, x) 是一个元组！→ (10, 5) → 解包给 x, y
print("x, y = y, x")
print("交换后：x =", x, ", y =", y)
print()

# ============================================
# 模式6：元组作为函数返回值
# ============================================
print("===== 模式6：元组作为函数返回值 =====")
t.penup()
t.goto(-350, -80)
t.pendown()
t.color("magenta")
t.write("函数返回元组 → 解包接收", font=("Arial", 10, "normal"))

def get_position():
    """返回一个坐标元组"""
    return (300, 150)

# 接收返回值并解包
pos_x, pos_y = get_position()
print("get_position() = (300, 150)")
print("pos_x =", pos_x, ", pos_y =", pos_y)

t.penup()
t.goto(0, -100)
t.pendown()
t.color("purple")
t.goto(pos_x, pos_y)
t.write("用函数返回的坐标移动", font=("Arial", 10, "normal"))
print()

# ============================================
# 实操①：创建位置元组
# ============================================
print("===== 实操①：创建位置元组 =====")
t.penup()
t.goto(-350, -160)
t.pendown()
t.color("blue")
t.write("实操①: 创建位置元组 → goto移动", font=("Arial", 10, "normal"))

# 创建 4 个位置元组，画一个矩形
p1 = (-100, -50)
p2 = (100, -50)
p3 = (100, 50)
p4 = (-100, 50)

print("p1 =", p1)
print("p2 =", p2)
print("p3 =", p3)
print("p4 =", p4)

t2 = turtle.Turtle()
t2.shape("turtle")
t2.speed(6)
t2.color("blue")
t2.penup()
t2.goto(p1)
t2.pendown()
t2.goto(p2)
t2.goto(p3)
t2.goto(p4)
t2.goto(p1)

t2.penup()
t2.goto(-80, 60)
t2.pendown()
t2.write("用元组画矩形", font=("Arial", 10, "normal"))
print()

# ============================================
# 实操②：索引取值——画折线
# ============================================
print("===== 实操②：索引取值 =====")

# 用元组存坐标列表，用索引取值画折线
points = ((-200, 100), (-50, 150), (100, 50), (200, 120))
print("points =", points)

t3 = turtle.Turtle()
t3.shape("turtle")
t3.speed(6)
t3.color("orange")
t3.penup()

# 用索引遍历元组（因为元组也支持 for 循环）
t3.goto(points[0])    # 跳到第一个点
t3.pendown()
for i in range(1, len(points)):
    t3.goto(points[i])  # 用索引取值

t3.penup()
t3.goto(-100, 180)
t3.pendown()
t3.write("实操②: points[0], points[1]... 画折线", font=("Arial", 9, "normal"))
print()

# ============================================
# 实操③：解包赋值——画星形
# ============================================
print("===== 实操③：解包赋值 =====")

t4 = turtle.Turtle()
t4.shape("turtle")
t4.speed(7)
t4.color("red")
t4.penup()

# 用元组存 5 个星形顶点坐标
star_points = [
    (0, 100),
    (-95, 30),
    (-59, -81),
    (59, -81),
    (95, 30)
]

# 用解包读取每个坐标
x0, y0 = star_points[0]
t4.goto(x0, y0)
t4.pendown()

for i in range(1, 6):
    x, y = star_points[i % 5]  # 解包！取模回到起点
    t4.goto(x, y)

t4.penup()
t4.goto(-80, 110)
t4.pendown()
t4.write("实操③: 解包画五角星", font=("Arial", 10, "normal"))
print()

# ============================================
# 实操④：尝试修改元组 → TypeError
# ============================================
print("===== 实操④：尝试修改元组 → TypeError =====")

test_tuple = (1, 2, 3)
print("test_tuple =", test_tuple)
print("尝试修改：test_tuple[0] = 100")
print("→ 会报 TypeError: 'tuple' object does not support item assignment")
print()
print("✅ 验证：元组确实不能修改！")

# 对比——列表可以修改
test_list = [1, 2, 3]
print("test_list =", test_list)
test_list[0] = 100
print("test_list[0] = 100  →", test_list, "← 列表可以修改！")
print()
print("✅ 元组 vs 列表：列表可变，元组不可变——这就是最大区别！")

t.penup()
t.goto(-350, -250)
t.pendown()
t.color("black")
t.write("元组=只读列表，不能增删改！  解包=元组拆开赋给多个变量", font=("Arial", 10, "bold"))

# ============================================
# 小测
# ============================================
print()
print("=" * 50)
print("📝 小测")
print("=" * 50)
print()
print("问题：元组和列表最大的区别是什么？")
print()
print("答案：")
print("  列表 list  → 可变（可以添加、删除、修改元素）")
print("  元组 tuple → 不可变（不能添加、删除、修改元素）")
print()
print("  元组 = 只读列表！")
print()
print("记忆钩子：")
print("  元组 = 只读列表，不能增删改")
print("  解包 = 元组拆开赋给多个变量")
print()

turtle.done()
