# GESP等级：四级 | GESP Python 四级考点
"""
==============================================================
14_异常处理 / 第2节_finally综合
    04_实操与测验.py
    时间段: 40:00-60:00  实操 + 小测
    ① 填空: finally的作用
    ② 补全: try-except-finally
    ③ 保护: input画图程序
    ④ 小测: finally什么时候执行?
    记忆钩子: finally = 不管怎样都要做的事
==============================================================
"""

import turtle

# ════════════════════════════════════════════════════════════════
# 实操一: 填空题 —— finally的作用
# ════════════════════════════════════════════════════════════════
print("=" * 58)
print("📝 实操一: 填空题 —— 理解finally的作用")
print("=" * 58)

fill_in_blanks = """
请补全下面句子:

  1. 无论 try 块中是否发生______，finally 块中的代码都会执行。
  2. finally 块通常用于执行______操作，例如关闭文件、清理屏幕。
  3. 即使 try 或 except 中有______语句，finally 也会先执行。
  4. try-except-finally 完整结构中，______块必须放在所有 except 之后。
  5. 记忆钩子: finally = ______

参考答案:
  [1] 异常  [2] 收尾/清理  [3] return  [4] finally  [5] 不管怎样都要做的事
"""

print(fill_in_blanks)

# 交互式填空题
print("── 交互版: 输入答案验证（输入 q 跳过）──")
print("")

def interactive_fill():
    """交互填空题: 用户输入答案，程序给出反馈。"""
    questions = [
        ("Q1: finally 中的代码在什么时候执行？", "异常", ["异常发生时", "异常不发生时", "无论是否发生异常"], 2),
        ("Q2: finally 主要用于什么操作？", "清理", ["计算", "清理", "循环"], 1),
        ("Q3: try/except 中有 return，finally 还会执行吗？", "会", ["会", "不会"], 0),
    ]

    score = 0
    for i, (q, ans, options, correct_idx) in enumerate(questions, 1):
        print(f"\n{q}")
        for idx, opt in enumerate(options):
            print(f"  {idx}. {opt}")
        try:
            choice = input("请选择序号(0/1/2): ").strip()
            if choice.lower() == 'q':
                print("  → 跳过本题")
                continue
            choice_idx = int(choice)
            if choice_idx == correct_idx:
                print("  ✅ 正确！")
                score += 1
            else:
                print(f"  ❌ 错误。正确答案是: {options[correct_idx]}")
        except (ValueError, IndexError):
            print("  输入无效，跳过本题。")

    print(f"\n得分: {score}/{len(questions)}")

# 取消注释运行交互版
# interactive_fill()


# ════════════════════════════════════════════════════════════════
# 实操二: 补全 try-except-finally
# ════════════════════════════════════════════════════════════════
print("\n" + "=" * 58)
print("📝 实操二: 补全 try-except-finally 结构")
print("=" * 58)

print("""
【题目】以下代码不完整，请补全缺失的部分。

  补全前（有缺陷的代码）:
  ┌─────────────────────────────────────────────┐
  │  def divide(a, b):                          │
  │      result = a / b      # 可能ZeroDivision  │
  │      print(f"结果: {result}")                │
  │      print("运算结束")   # 出错时不执行      │
  └─────────────────────────────────────────────┘

  请补全为 try-except-finally 结构，确保:
    ① 捕获 ZeroDivisionError 并提示"除数不能为0"
    ② 不管是否出错都打印"运算结束"
""")

def divide_before():
    """补全前的版本: 没有异常保护。"""
    a = int(input("请输入被除数: "))
    b = int(input("请输入除数: "))
    # 如果 b=0，这里直接崩溃，下面的print不会执行
    result = a / b
    print(f"结果: {result}")
    print("运算结束")   # 这句可能被跳过


# ── 参考答案 ──
def divide_after():
    """补全后的版本: 用 try-except-finally 保护。"""
    try:
        a = int(input("请输入被除数: "))
        b = int(input("请输入除数: "))
        result = a / b
        print(f"结果: {result}")
    except ZeroDivisionError:
        print("除数不能为0！")
    except ValueError:
        print("请输入有效的整数！")
    finally:
        print("运算结束")   # ★ 无论如何都会执行


# 取消注释运行补全后的版本
# divide_after()


print("""
【进阶题】请补全以下 turtle 画正方形的代码:

  t = turtle.Turtle()
  try:
      n = int(input("请输入边长: "))
      for _ in range(4):
          t.forward(_____)    # ① 填什么？
          t.right(_____)      # ② 填什么？
  except ___________:         # ③ 捕获什么异常？
      print("请输入数字!")
  ________:                   # ④ 填什么关键字？
      print("画图结束")
      turtle.done()

参考答案:
  ① n       ② 90       ③ ValueError       ④ finally
""")


# ════════════════════════════════════════════════════════════════
# 实操三: 保护 input 画图程序 —— 完整实战
# ════════════════════════════════════════════════════════════════
print("\n" + "=" * 58)
print("📝 实操三: 保护 input 画图程序")
print("=" * 58)

print("""
【任务】用 try-except-finally 保护一个画图程序。

  用户输入:
    1. 图形类型 (circle / square / triangle)
    2. 尺寸 (数字)

  要求:
    - 输入非数字 → 捕获 ValueError
    - 输入不支持的类型 → 给出友好提示
    - 无论成功/失败 → finally 清屏并输出"程序结束"
""")

def protected_drawing():
    """完整实战: 保护 input 画图程序。"""
    t = turtle.Turtle()
    t.speed(5)
    t.pensize(3)
    turtle.title("protected drawing 实战")

    try:
        # 输入图形类型
        shape = input("请输入图形类型 (circle / square / triangle): ").strip().lower()
        # 输入尺寸
        size = int(input("请输入尺寸: "))

        # 根据类型画图
        if shape == "circle":
            t.circle(size)
        elif shape == "square":
            for _ in range(4):
                t.forward(size)
                t.right(90)
        elif shape == "triangle":
            for _ in range(3):
                t.forward(size)
                t.left(120)
        else:
            # 不支持的图形类型，手动抛出异常到 except
            raise ValueError(f"不支持的图形类型: {shape}")

    except ValueError as e:
        # 捕获 int() 转换失败 以及 自定义的图形类型错误
        print(f"输入错误: {e}")
    except turtle.TurtleGraphicsError as e:
        print(f"绘图错误: {e}")
    else:
        print(f"✅ 成功画了一个 {shape}，尺寸为 {size}")
    finally:
        # ★ 最终收尾: 清屏 + 关闭
        print("【finally】清理屏幕...")
        t.clear()
        t.hideturtle()
        print("【finally】程序结束")

    turtle.done()


# 取消注释运行完整实战
# protected_drawing()


# ════════════════════════════════════════════════════════════════
# 实操四: 找bug —— 以下代码有什么问题？
# ════════════════════════════════════════════════════════════════
print("\n" + "=" * 58)
print("📝 实操四: 找bug —— 以下代码错在哪里？")
print("=" * 58)

buggy_code = """
【有bug的代码】

  try:
      f = open("data.txt", "r")
      content = f.read()
      print(content)
  except FileNotFoundError:
      print("文件未找到")
      f.close()             # ① 问题: 文件没打开, f不存在
  finally:
      f.close()             # ② 问题: 如果打开失败, f也不存在

【正确答案】

  f = None
  try:
      f = open("data.txt", "r")
      content = f.read()
      print(content)
  except FileNotFoundError:
      print("文件未找到")
  finally:
      if f is not None:     # ★ 判空保护
          f.close()
          print("文件已关闭")
"""

print(buggy_code)


# ════════════════════════════════════════════════════════════════
# 小测: finally 什么时候执行？
# ════════════════════════════════════════════════════════════════
print("=" * 58)
print("🎯 小测: finally 什么时候执行？")
print("=" * 58)

quiz = """
选择题:

  1. 以下哪种情况 finally 不会执行？
     A. try 正常执行完毕
     B. except 捕获了异常
     C. try 中抛出了未被捕获的异常
     D. 调用 sys.exit()
     E. 以上都会执行

  2. 如果 try 中有 return 语句，finally 会？
     A. 不执行
     B. 在 return 之前执行
     C. 在 return 之后执行
     D. 不确定

  3. try-except-finally 中 else 块的作用是？
     A. 不管有没有异常都执行
     B. 有异常时执行
     C. 没有异常时执行
     D. 和 finally 一样

答案: 1.E  2.B  3.C
"""

print(quiz)

print("""
【解析】
  第1题: 几乎所有情况 finally 都会执行，
         但 sys.exit() 会直接终止解释器，
         os._exit() 也会跳过 finally。
         更准确的答案是: 除了强制终止进程外，finally都会执行。

  第2题: 即使 try 中有 return，Python 也会先执行 finally，
         再真正返回。这被称为"finally 的保底机制"。

  第3题: else 只在 try 没有异常时执行，
         finally 不管有没有异常都执行。
         两者功能不同，不要混淆。
""")


# ════════════════════════════════════════════════════════════════
# 记忆钩子 + 总结
# ════════════════════════════════════════════════════════════════
print("=" * 58)
print("💡 记忆钩子 + 本章总结")
print("=" * 58)

summary = """
┌────────────────────────────────────────────────────────────┐
│  【记忆钩子】                                               │
│  finally = 不管怎样都要做的事                                │
│                                                            │
│  想象你出门时:                                               │
│    ① try:    尝试去超市买东西                                │
│    ② except: 如果东西卖完了，换个店买                        │
│    ③ else:   买到东西了，开心回家                            │
│    ④ finally: 不管买没买到，都要锁门回家 ← 必须做的事        │
│                                                            │
│  【执行路径总结】                                           │
│                                                            │
│   try成功 ──→ else ──→ finally                              │
│   try出错 ──→ except ──→ finally                           │
│   try出错(未捕获) ──→ finally ──→ 崩溃                      │
│   try有return ──→ finally ──→ return                        │
│                                                            │
│  【应用场景】                                               │
│    ✅ 关闭文件  ✅ 清屏/重置  ✅ 数据库断开                  │
│    ✅ 释放锁    ✅ 关闭网络连接  ✅ 记录日志                  │
└────────────────────────────────────────────────────────────┘
"""

print(summary)


# ════════════════════════════════════════════════════════════════
# 附加挑战（选做）
# ════════════════════════════════════════════════════════════════
print("=" * 58)
print("🏆 附加挑战（选做）")
print("=" * 58)

print("""
【挑战1】finally 中可以有 continue/break 吗？试试效果。

【挑战2】try 块出现异常被 except 捕获后，finally 中再次抛出异常会怎样？

【挑战3】写一个 计算器程序，支持 + - * / 四种运算，
         用 try-except-finally 保护:
           - 输入非数字 → ValueError
           - 除以0 → ZeroDivisionError
           - 不支持的运算符 → 自定义异常
           - 无论结果 → finally 打印"计算结束"

【挑战4】为什么以下代码中 finally 修改了返回值？

  def func():
      try:
          return 1
      finally:
          return 2
  print(func())   # 输出多少？为什么？
""")
