# GESP等级：四级 | GESP Python 四级考点
# -*- coding: utf-8 -*-
"""
11第3节4 — 参数进阶：位置参数 / 关键字参数 / 默认参数 / *args
课程：11_函数 → 第3节_参数进阶
GESP 考点：#函数参数传递
=========================================================
"""
import turtle
import time

# =====================================================
# 【导入】00:00-10:00  函数参数有几种写法？
# =====================================================
print("=" * 60)
print("【导入】函数参数有几种写法？—— 位置/关键字/默认值")
print("=" * 60)
print()

print("前面学了 def 定义函数，但参数只能硬写吗？")
print()
print("看这句：")
print("  def greet(name):          # name 是'位置参数'")
print("      print(f'你好，{name}！')")
print()
print("调用时：")
print("  greet('小明')             # 必须按顺序传一个值")
print()

# 展示四种写法
print("其实，函数参数有 4 种写法：")
print(" ① 位置参数   →  按顺序传值")
print(" ② 关键字参数  →  按名字传值（name=value）")
print(" ③ 默认参数   →  定义时给默认值（不传就用默认）")
print(" ④ *args多参数 →  收一堆值打包成元组")
print()

input("按回车键进入知识点学习...")

# =====================================================
# 【知识点】10:00-30:00  四种参数详解
# =====================================================
print()
print("=" * 60)
print("【知识点】四种函数参数")
print("=" * 60)

# --- 知识点① 位置参数 ---
print()
print("① 位置参数（Positional Arguments）—— 按顺序")
print("-" * 50)
print("  语法： def func(参数1, 参数2, ...)")
print("  特点：")
print("    • 调用时按定义时的顺序传值")
print("    • 顺序错了，结果就错了")
print("    • 最基础、最常用的传参方式")
print()

def introduce(name, age, city):
    """位置参数——按顺序传入"""
    print(f"  大家好，我叫{name}，今年{age}岁，来自{city}。")

print("  def introduce(name, age, city):")
print("      print(f'大家好，我叫{name}，今年{age}岁，来自{city}。')")
print()
print("  正确顺序调用：")
introduce("小明", 10, "北京")
print()
print("  错误顺序调用（顺序反了）：")
try:
    introduce("北京", 10, "小明")  # 顺序错误
except Exception as e:
    print(f"  → 报错：{e}")
print("  （虽然不报错，但意思完全变了！）")
print()

input("按回车键继续学习知识点②...")

# --- 知识点② 关键字参数 ---
print()
print("-" * 50)
print("② 关键字参数（Keyword Arguments）—— 按名字")
print("-" * 50)
print("  语法： func(参数名=值, 参数名=值)")
print("  特点：")
print("    • 用参数名指定要传给谁")
print("    • 顺序可以打乱！")
print("    • 代码更清晰，一看就知道每个值的意思")
print()

def show_student(name, score, subject):
    """关键字参数可以打乱顺序"""
    print(f"  学生：{name}，{subject}成绩：{score}分")

print("  def show_student(name, score, subject):")
print()
print("  # 关键字参数调用（顺序打乱也不怕）：")
show_student(subject="数学", name="小红", score=95)
print()

print("  对比位置参数：")
print("  show_student('小红', 95, '数学')  # 必须按顺序")
show_student("小红", 95, "数学")
print()
print("  关键字参数的好处：")
print("  ✓ 不用记参数的顺序")
print("  ✓ 代码可读性更高")
print("  ✓ 可以只传部分参数（配合默认参数）")
print()

# 混合使用
print("  混用规则：位置参数在前，关键字参数在后！")
print("  show_student('小刚', subject='英语', score=88)  # ✓ 正确")
show_student("小刚", subject="英语", score=88)
print()
print("  show_student(subject='英语', '小刚', 88)        # ✗ 错误！")
print("  → SyntaxError: positional argument follows keyword argument")
print()

input("按回车键继续学习知识点③...")

# --- 知识点③ 默认参数 ---
print()
print("-" * 50)
print("③ 默认参数（Default Arguments）—— 有默认值")
print("-" * 50)
print("  语法： def func(参数名=默认值)")
print("  特点：")
print("    • 定义函数时给参数一个默认值")
print("    • 调用时不传 → 用默认值")
print("    • 调用时传了  → 用传入的值")
print("    • 让函数更灵活，调用更简洁")
print()

def greet(name, greeting="你好"):
    """默认参数——greeting 有默认值"""
    print(f"  {greeting}，{name}！")

print("  def greet(name, greeting='你好'):")
print()
print("  # 不传 greeting → 用默认值'你好'")
greet("小明")
print()
print("  # 传了 greeting → 用传入的值")
greet("小红", "Hello")
greet("小刚", greeting="嗨")
print()

# 默认参数必须在位置参数后面
print("  规则：默认参数必须放在位置参数后面！")
print("  def func(a, b=10):      # ✓ 正确")
print("  def func(a=10, b):      # ✗ 错误！")
print()

# 常见陷阱：默认参数是可变对象
print("  ⚠ 小心陷阱：默认参数不要用可变对象（如列表）")
print()

def add_item(item, items=[]):
    """危险：默认参数是可变对象"""
    items.append(item)
    return items

print("  def add_item(item, items=[]):")
print("      items.append(item)")
print("      return items")
print()
print("  第一次调用：", add_item("苹果"))
print("  第二次调用：", add_item("香蕉"))
print("  第三次调用：", add_item("橘子"))
print()
print("  发现了吗？每次调用共用同一个列表！")
print("  正确做法：默认值用 None，函数内创建新列表")
print()

def add_item_safe(item, items=None):
    """安全：用 None 作为默认值"""
    if items is None:
        items = []
    items.append(item)
    return items

print("  def add_item_safe(item, items=None):")
print("      if items is None:")
print("          items = []")
print("      items.append(item)")
print("      return items")
print()
print("  第一次：", add_item_safe("苹果"))
print("  第二次：", add_item_safe("香蕉"))
print("  第三次：", add_item_safe("橘子"))
print()

input("按回车键继续学习知识点④...")

# --- 知识点④ *args 多参数 ---
print()
print("-" * 50)
print("④ *args — 多参数（收一堆值）")
print("-" * 50)
print("  语法： def func(*args)")
print("  特点：")
print("    • 可以接收任意数量的位置参数")
print("    • 所有参数被打包成一个元组（tuple）")
print("    • 名字不一定叫 args，但 * 是必需的")
print("    • 通常约定叫 *args")
print()

def sum_all(*args):
    """*args 接收多个数字，求和"""
    total = sum(args)
    print(f"  传入的参数：{args}")
    print(f"  求和结果：{total}")
    return total

print("  def sum_all(*args):")
print("      total = sum(args)")
print("      print(f'传入的参数：{args}')")
print("      return total")
print()
print("  # 可以传任意数量的参数：")
sum_all(1, 2, 3)
print()
sum_all(10, 20, 30, 40, 50)
print()
sum_all(5)  # 只传一个也可以
print()
sum_all()   # 不传也可以（空元组）
print()

def make_sentence(*words):
    """用 *args 拼接句子"""
    sentence = " ".join(words)
    print(f"  合成句子：{sentence}")
    return sentence

print("  def make_sentence(*words):")
print("      sentence = ' '.join(words)")
print()
make_sentence("今天", "天气", "真不错")
make_sentence("Python", "是", "一门", "很", "有趣", "的", "语言")
print()

# 混合使用
print("  *args 可以和普通参数混用：")
print("  def show_info(title, *items):")
print()

def show_info(title, *items):
    print(f"  【{title}】")
    for i, item in enumerate(items, 1):
        print(f"    {i}. {item}")

show_info("水果列表", "苹果", "香蕉", "橘子", "西瓜")
print()


# --- 知识点⑤ 值传递 vs 引用传递 ---
print()
print("-" * 50)
print("⑤ 值传递 vs 引用传递 —— 复印件 vs 共用笔记本")
print("-" * 50)
print("  ⭐ GESP四级考点：不可变类型传值，可变类型传引用")
print()
print("  📌 值传递（不可变类型：int/str/float/bool/tuple）")
print("     = 给你一张复印件，你在复印件上乱画，原件不变")
print()

def demo_value_pass():
    n = 10
    print(f"    调用前：n = {n}")
    def add_one(x):
        x = x + 1
        print(f"    函数内部：x = {x}")
    add_one(n)
    print(f"    调用后：n = {n}  ← 还是10！复印件随便改不影响原件")

demo_value_pass()
print()

print("  📌 引用传递（可变类型：list/dict/set）")
print("     = 共用一本笔记本，你在上面写东西，我也能看到")
print()

def demo_ref_pass():
    my_list = ["苹果", "香蕉"]
    print(f"    调用前：my_list = {my_list}")
    def add_item(items):
        items.append("新东西")
        print(f"    函数内部：items = {items}")
    add_item(my_list)
    print(f"    调用后：my_list = {my_list}  ← 变了！共用笔记本！")

demo_ref_pass()
print()

print("  🎯 记忆口诀：")
print("    int/str/float/bool/tuple → 传复印件（值传递）")
print("    list/dict/set            → 传原件（引用传递）")
print()
print("  🖐️ 身体锚点：")
print("    值传递 = 双手做复印动作（压一下）")
print("    引用传递 = 双手摊开（分享出去）")
print()

input("按回车键进入演示环节...")

# =====================================================
# 【演示】30:00-40:00  draw_star —— 默认参数实战
# =====================================================
print()
print()
print("=" * 60)
print("【演示】用 turtle 画星星——默认参数的妙用")
print("=" * 60)
print()
print("我们定义一个 draw_star 函数：")
print("  def draw_star(size=50, color='gold', x=0, y=0):")
print("      用 turtle 画一颗五角星")
print()
print("参数说明：")
print("  size  → 星星的大小（默认 50）")
print("  color → 星星的颜色（默认 gold）")
print("  x, y  → 星星的位置（默认 0,0）")
print()

# 创建 turtle
t = turtle.Turtle()
t.speed(5)
t.pensize(2)

def draw_star(size=50, color="gold", x=0, y=0):
    """画一颗五角星——全部有默认值"""
    t.penup()
    t.goto(x, y)
    t.pendown()
    t.color(color)
    t.begin_fill()
    for _ in range(5):
        t.forward(size)
        t.right(144)  # 五角星转角 144°
    t.end_fill()
    t.penup()
    # 在星星上方标注参数
    t.goto(x, y + size // 2)
    t.color("black")
    t.write(f"size={size}, color='{color}'",
            align="center", font=("Arial", 8, "normal"))

print("一、只传 size（其他用默认值）：")
draw_star(size=30)
time.sleep(1)

print("二、只改 color（size 用默认）：")
draw_star(color="red", x=80, y=0)
time.sleep(1)

print("三、两个都改：")
draw_star(size=60, color="blue", x=-100, y=-80)
time.sleep(1)

print("四、全用默认值：")
draw_star(x=100, y=-80)
time.sleep(1)

print("五、只传位置参数：")
draw_star(40, "purple", -50, 80)
time.sleep(1)

print()
print("观察上面的五角星，总结：")
print("  ✓ 默认参数让函数调用更简洁")
print("  ✓ 关键字参数让我们只改想改的参数")
print("  ✓ size=50 是默认参数，不传就等于 50")
print()

input("按回车键进入实操环节...")
t.clear()

# =====================================================
# 【实操①】40:00-45:00  位置 vs 关键字参数填空
# =====================================================
print()
print("=" * 60)
print("【实操①】位置参数 vs 关键字参数 —— 填空")
print("=" * 60)
print()

print("题目 1：下面函数定义中，name 是______参数，age 是______参数。")
print()
print("  def show_person(name, age=18):")
print("      print(f'{name} is {age} years old')")
print()
print("  你的答案：name 是______参数，age 是______参数")
print("  （正确答案：位置参数 / 默认参数）")
print()

input("按回车键继续第2题...")
print()

print("题目 2：补全调用代码——用关键字参数调用")
print()
print("  def calc(a, b, c):")
print("      return a * 10 + b * 5 + c")
print()
print("  # 请用关键字参数调用，a=3, b=2, c=1")
print("  result = calc(______)")
print()
print("  你的答案：calc(a=3, b=2, c=1)")
print()

input("按回车键继续第3题...")
print()

print("题目 3：下面代码会报错吗？为什么？")
print()
print("  def func(a, b, c=10):")
print("      print(a, b, c)")
print()
print("  func(1, c=5, 2)   # 这行代码会怎样？")
print()
print("  你的答案：____________________")
print()
time.sleep(1)
print("  答案：会报错！位置参数必须在关键字参数前面。")
print("  SyntaxError: positional argument follows keyword argument")
print("  应该写成：func(1, 2, c=5)")
print()

input("按回车键继续实操②...")

# =====================================================
# 【实操②】45:00-50:00  用默认参数简化函数调用
# =====================================================
print()
print("=" * 60)
print("【实操②】用默认参数简化函数调用")
print("=" * 60)
print()

print("场景：设计一个 '制作饮料' 函数")
print()

# 先展示没有默认参数的版本
print("版本 A：没有默认参数——每次都要传所有值")
print()

def make_drink_no_default(name, temperature, sugar, ice, cup_size):
    """无默认参数版本"""
    desc = f"【{cup_size}杯】{temperature}{name}，"
    desc += f"{'加糖' if sugar else '无糖'}，"
    desc += f"{'加冰' if ice else '去冰'}"
    print(f"  制作：{desc}")

print("  def make_drink(name, temperature, sugar, ice, cup_size):")
print("      ...")
print()
print("  每次调用都要写5个参数：")
make_drink_no_default("柠檬茶", "热", True, False, "大")
make_drink_no_default("奶茶", "常温", True, False, "中")
print()

print("版本 B：用默认参数——只需要传不同的值")
print()

def make_drink(name, temperature="常温", sugar=True, ice=False, cup_size="中"):
    """有默认参数版本"""
    desc = f"【{cup_size}杯】{temperature}{name}，"
    desc += f"{'加糖' if sugar else '无糖'}，"
    desc += f"{'加冰' if ice else '去冰'}"
    print(f"  制作：{desc}")
    return desc

print("  def make_drink(name, temperature='常温', sugar=True,")
print("                 ice=False, cup_size='中'):")
print()
print("  # 只传必填参数（name），其他用默认值：")
make_drink("奶茶")
print()
print("  # 想改某个参数，用关键字指定：")
make_drink("柠檬茶", temperature="冰")
make_drink("咖啡", sugar=False, cup_size="小")
make_drink("果茶", temperature="热", cup_size="大")
print()
print("  对比总结：")
print("  ✓ 无默认参数 → 每次都要写5个参数")
print("  ✓ 有默认参数 → 只传你想改的！")
print()

input("按回车键继续实操③...")

# =====================================================
# 【实操③】50:00-55:00  补全多参数代码
# =====================================================
print()
print("=" * 60)
print("【实操③】补全多参数 *args 代码")
print("=" * 60)
print()

print("题目 1：补全代码——用 *args 计算平均值")
print()

print("  def average(______):")
print("      '''计算传入所有数字的平均值'''")
print("      if len(______) == 0:")
print("          return 0")
print("      total = sum(______)")
print("      return total / len(______)")
print()
print("  # 测试")
print("  print(average(10, 20, 30))           # 20.0")
print("  print(average(5, 15, 25, 35, 45))    # 25.0")
print("  print(average())                      # 0")
print()
print("  你的答案：")
time.sleep(1)
print("  → 全部填 *args（或 *numbers）")
print()

# 正确答案展示
def average(*args):
    if len(args) == 0:
        return 0
    total = sum(args)
    return total / len(args)

print(f"  average(10, 20, 30)        = {average(10, 20, 30)}")
print(f"  average(5, 15, 25, 35, 45) = {average(5, 15, 25, 35, 45)}")
print(f"  average()                  = {average()}")
print()

input("按回车键继续题目2...")
print()

print("题目 2：补全代码——*args 让 print 打印多个值")
print()

def print_all(*items, prefix=">> "):
    """*args 接收多个值，加上前缀打印"""
    for item in items:
        print(f"  {prefix}{item}")

print("  def print_all(______, prefix='>> '):")
print("      for item in ______:")
print("          print(f'{prefix}{item}')")
print()
print("  print_all('苹果', '香蕉', '橘子')")
print("  print_all(1, 2, 3, 4, 5, prefix='# ')")
print()
print("  你的答案：")
time.sleep(1)
print("  → 第一个空填 *items，第二个空填 items")
print()

# 运行展示
print("  运行结果：")
print_all("苹果", "香蕉", "橘子")
print_all(1, 2, 3, 4, 5, prefix="# ")
print()

input("按回车键继续题目3...")
print()

print("题目 3：补全代码——混合使用普通参数和 *args")
print()
print("  def make_menu(restaurant, ______):")
print("      '''餐厅菜单——*args 接收菜品列表'''")
print("      print(f'【{restaurant}】菜单：')")
print("      for i, dish in enumerate(______, 1):")
print("          print(f'  {i}. {dish}')")
print()
print("  make_menu('川菜馆', '回锅肉', '麻婆豆腐', '水煮鱼')")
print("  make_menu('甜品店', '蛋糕', '奶茶')")
print()
print("  你的答案：")
time.sleep(1)
print("  → 第一个空填 *dishes，第二个空填 dishes")
print()

# 运行展示
def make_menu(restaurant, *dishes):
    print(f"  【{restaurant}】菜单：")
    for i, dish in enumerate(dishes, 1):
        print(f"    {i}. {dish}")

print("  运行结果：")
make_menu("川菜馆", "回锅肉", "麻婆豆腐", "水煮鱼")
print()
make_menu("甜品店", "蛋糕", "奶茶")
print()

input("按回车键进行小测...")

# =====================================================
# 【小测】55:00-60:00  "关键字参数的好处？"
# =====================================================
print()
print("=" * 60)
print("【小测】学完啦！来测一测")
print("=" * 60)
print()

print("题目 1：关键字参数的最大好处是什么？")
print()
print("  A. 可以让参数变多")
print("  B. 不用记住参数顺序，代码更清晰")
print("  C. 可以不传参数")
print("  D. 只能用关键字，不能混合使用")
print()
print("  你的答案：___")
print()
input("按回车键查看答案...")
print()
print("  正确答案：B")
print()
print("  解析：")
print("    关键字参数通过 name=value 的方式传参：")
print("    ✓ 不用记参数的顺序   ← 最大好处！")
print("    ✓ 代码自解释，可读性高")
print("    ✓ 可以配合默认参数跳过某些参数")
print()

print("题目 2：下面哪个是 *args 的正确描述？")
print()
print("  A. 只能接收一个参数")
print("  B. 把接收的参数打包成列表")
print("  C. 把接收的参数打包成元组")
print("  D. 只能接收字符串")
print()
print("  你的答案：___")
print()
input("按回车键查看答案...")
print()
print("  正确答案：C")
print()

print("题目 3：以下代码的输出是什么？")
print()
print("  def test(a, b=10, c=20):")
print("      print(a, b, c)")
print()
print("  test(5, c=30)")
print()
print("  A. 5 10 30")
print("  B. 5 30 20")
print("  C. 5 10 20")
print("  D. 报错")
print()
print("  你的答案：___")
print()
input("按回车键查看答案...")
print()
print("  正确答案：A")
print("  解析：a=5 是位置参数，b 没传用默认值 10，c=30 用关键字传入")
print()

# =====================================================
# 总结
# =====================================================
print()
print("=" * 60)
print("【课堂总结】—— 函数参数进阶")
print("=" * 60)
print()
print("  学了 5 种参数知识：")
print()
print("  ① 位置参数  → 按顺序传，最基础")
print("      def func(a, b):   func(1, 2)")
print()
print("  ② 关键字参数 → 按名字传，不用记顺序")
print("      func(b=2, a=1)")
print()
print("  ③ 默认参数  → 定义时给默认值，调用更简洁")
print("      def func(a, b=10):   func(5)")
print()
print("  ④ *args 多参数 → 收一堆值，打包成元组")
print("      def func(*args):   func(1, 2, 3)")
print()
print("  ⑤ 值传递 vs 引用传递 → 复印件 vs 共用笔记本")
print("      int/str/float/bool/tuple → 传值")
print("      list/dict/set            → 传引用")
print()
print("  混用规则：")
print("    位置参数在前，关键字参数在后")
print("    默认参数在位置参数后面")
print("    *args 通常在最后（但可以在关键字参数前）")
print()
print("记忆钩子（背下来！）：")
print("  ★ 位置按顺序  关键字按名字  ★")
print("  ★ 默认有默认值  多参数收所有  ★")
print()
print("  谐音记忆：")
print('    "位置" = 去食堂打饭要排队，按顺序')
print('    "关键字" = 叫名字，说"我要小明的饭"')
print('    "默认" = 食堂师傅默认加葱，你说不要才不加')
print('    "*args" = "收容所"，什么都收')
print()

# turtle 画总结图
t.clear()
t.penup()
t.goto(0, 150)
t.color("blue")
t.write("位置按顺序", align="center", font=("Arial", 18, "bold"))
t.goto(0, 110)
t.color("red")
t.write("关键字按名字", align="center", font=("Arial", 18, "bold"))
t.goto(0, 70)
t.color("green")
t.write("默认有默认值", align="center", font=("Arial", 18, "bold"))
t.goto(0, 30)
t.color("purple")
t.write("多参数收所有 (*args)", align="center", font=("Arial", 18, "bold"))

t.goto(0, -30)
t.color("orange")
t.write("记忆钩子", align="center", font=("Arial", 14, "bold"))

t.goto(0, -70)
t.color("black")
t.write("位置参数在前，关键字参数在后", align="center", font=("Arial", 12, "normal"))
t.goto(0, -100)
t.color("black")
t.write("默认参数在位置参数后面", align="center", font=("Arial", 12, "normal"))

# 画框装饰
t.penup()
t.goto(-200, 170)
t.pendown()
t.color("gray")
for _ in range(2):
    t.forward(400)
    t.right(90)
    t.forward(300)
    t.right(90)
t.penup()

print("=" * 60)
print()
print("课程结束！记得完成练习册和课后练习卡。")
print()

turtle.done()
