第3章 3.2 参数进阶:默认/位置/关键字/不定

上一章我们学会了用 def 定义函数,感觉像是学会了「怎么给机器下达指令」。但你有没有遇到过这种情况——写了一个函数,朋友拿来用时忘记传参数,或者传参数顺序搞混了,结果程序报错?

今天我们就来解决这个问题。学完这一章,你的函数会变得「智能」——能记住默认值、能接受任意数量的参数、还能让调用者用「名字=值」的方式传参。

🎯 开场 3 分钟:为什么要学这个?

场景:你写了一个「发送消息」的函数,最初只有 2 个参数(收件人、内容)。后来加了「是否加密」「优先级」「抄送人」等 5 个参数。结果每次调用都要写一长串:

send_message("张三", "你好", True, "high", ["李四"], "urgent")

这样用的人崩溃,你也崩溃——根本不知道每个 True/"high" 是什么意思。

学完本文你能:
- 让参数有默认值,调用时可以省略
- 用 *args 接收「想传多少传多少」的参数
- 用 **kwargs 接收「名字=值」形式的参数
- 避免默认参数陷阱(这是新手必踩的坑!)


🧱 基础 25 分钟:核心概念

什么是默认参数?

类比:你去奶茶店点单,服务员问「大杯还是中杯」,如果你不说,默认给你中杯。这个「中杯」就是默认值

为什么用:有些参数 90% 的情况都是一个值,没必要每次调用都重复写。

def 打招呼(姓名, 语气="友好"):
print(f"{语气}地打招呼:你好,{姓名}!")

打招呼("小明")              # 用默认值
打招呼("小红", "热情地")    # 覆盖默认值

输出:

友好地打招呼:你好,小明!
热情地打招呼:你好,小红!

什么是位置参数?

类比:你去医院挂号,医生说「第几号就第几号,按顺序来」。位置参数就是「谁在前面谁先匹配」。

def 计算BMI(身高米, 体重公斤):
return 体重公斤 / (身高米 ** 2)

bmi = 计算BMI(1.75, 70)  # 位置必须对应:身高在前,体重在后
print(f"BMI = {bmi:.2f}")

输出:

BMI = 22.86

❌ 常见错误:把位置搞混

bmi = 计算BMI(70, 1.75)  # 错!70 被当成身高,1.75 被当成体重
print(f"BMI = {bmi:.2f}")

输出:

BMI = 22.86

等等,这个竟然「看起来对了」?但物理意义完全错了——你的身高变成 70 米,体重变成 1.75 公斤。这就是位置参数的坑:不会报错,但结果荒谬

什么是关键字参数?

类比:点外卖时你说「备注:不要辣」「备注:多加醋」,不用管商家内部字段的顺序。关键字参数就是「指名道姓地赋值」。

def 订外卖(主餐, 饮料="可乐", 甜点="蛋糕", 备注=""):
print(f"主餐:{主餐}")
print(f"饮料:{饮料}")
print(f"甜点:{甜点}")
print(f"备注:{备注}")

订外卖("黄焖鸡", 甜点="冰淇淋", 饮料="雪碧")  # 只改甜点和饮料

输出:

主餐:黄焖鸡
饮料:雪碧
甜点:冰淇淋
备注:

注意!关键字参数可以打乱顺序,因为有「名字」就不会混淆。

什么是不定参数 *args?

类比:你请客吃饭,来多少人你就准备多少碗筷。「不定」就是「来多少吃多少」。

def 求和(*数字):
print(f"收到了 {len(数字)} 个数字:{数字}")
return sum(数字)

result = 求和(1, 2, 3, 4, 5)
print(f"总和 = {result}")

输出:

收到了 5 个数字:(1, 2, 3, 4, 5)
总和 = 15

*数字 把所有位置参数收集成一个元组。调用时可以传任意多个。

什么是不定参数 **kwargs?

类比:填表格时「姓名=张三」「年龄=25」「职业=老师」,每个字段都有名字。**kwargs 就是接收这种「名字=值」形式的参数。

def 打印学生信息(**信息):
for 键, 值 in 信息.items():
    print(f"{键}:{值}")

打印学生信息(姓名="小明", 年龄=18, 城市="北京", 爱好="篮球")

输出:

姓名:小明
年龄:18
城市:北京
爱好:篮球

**信息 把所有关键字参数收集成一个字典

配图1 - 配图1

强制关键字参数

类比:有些表单必须填,某些字段「只认名字不认顺序」,不能空着。

* 单独分隔,前面是位置参数,后面必须是关键字参数:

def 创建用户(用户名, *, 邮箱, 手机号):
print(f"用户名:{用户名}")
print(f"邮箱:{邮箱}")
print(f"手机号:{手机号}")

创建用户("张三", 邮箱="zhangsan@example.com", 手机号="13800138000")

❌ 如果不写关键字参数名,会报错:

创建用户("张三", "zhangsan@example.com", "13800138000")  # TypeError

参数解包(拆包)

类比:外卖送来一袋打包的食物,你要一个个拿出来。「解包」就是把打包的东西拆开。

def 介绍产品(名称, 价格, 评分):
print(f"{名称} - 价格:{价格}元 - 评分:{评分}分")

产品信息 = ["iPhone", 9999, 4.9]      # 列表
产品字典 = {"名称": "iPhone", "价格": 9999, "评分": 4.9}

介绍产品(*产品信息)        # 用 * 解包列表
介绍产品(**产品字典)       # 用 ** 解包字典

输出:

iPhone - 价格:9999元 - 评分:4.9分
iPhone - 价格:9999元 - 评分:4.9分

🔥 实战 35 分钟:3 个递进小项目

项目 1:个人待办清单管理器(5 分钟)

需求:添加待办、查看待办、删除待办。任务可以设置优先级。

"""个人待办清单 v1 - 演示默认参数和关键字参数"""

待办列表 = []

def 添加待办(内容, 优先级="中"):
"""添加一条待办,默认优先级为'中'"""
待办列表.append({"内容": 内容, "优先级": 优先级})
print(f"✅ 已添加:{内容}({优先级}优先级)")

def 查看待办():
"""查看所有待办"""
if not 待办列表:
    print("📝 待办清单是空的")
    return
print("\n📋 当前待办清单:")
for i, 待办 in enumerate(待办列表, 1):
    print(f"  {i}. {待办['内容']} [{待办['优先级']}]")

def 删除待办(序号):
"""删除指定序号的待办"""
if 1 <= 序号 <= len(待办列表):
    已删除 = 待办列表.pop(序号 - 1)
    print(f"🗑️ 已删除:{已删除['内容']}")
else:
    print(f"❌ 序号 {序号} 不存在")

# 演示
添加待办("写周报")
添加待办("回复邮件", "高")
添加待办("开会", 优先级="紧急")  # 用关键字参数指定
查看待办()
删除待办(1)
查看待办()

输出:

✅ 已添加:写周报(中优先级)
✅ 已添加:回复邮件(高优先级)
✅ 已添加:开会(紧急优先级)

📋 当前待办清单:
1. 写周报 [中]
2. 回复邮件 [高]
3. 开会 [紧急]
🗑️ 已删除:写周报
📋 当前待办清单:
1. 回复邮件 [高]
2. 开会 [紧急]

一句话解释:默认参数让「优先级」可以省略,关键字参数让调用时更易读。


项目 2:批量处理 CSV 数据(15 分钟)

需求:读取一个 CSV 文件,筛选符合条件的数据,输出统计报告。

先创建一个测试 CSV 文件 销售数据.csv

日期,商品,数量,单价
2024-01-01,iPhone,2,9999
2024-01-01,MacBook,1,12999
2024-01-02,iPad,5,4999
2024-01-02,iPhone,1,9999
2024-01-03,AirPods,10,1299

然后写处理脚本:

"""批量处理销售数据 - 演示 *args 和 **kwargs"""

import csv

def 读取CSV(文件路径):
"""读取 CSV 文件,返回列表"""
with open(文件路径, "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    return list(reader)

def 筛选数据(数据列表, **筛选条件):
"""根据条件筛选数据,支持任意数量的筛选条件"""
结果 = []
for 行 in 数据列表:
    符合条件 = True
    for 键, 值 in 筛选条件.items():
        if 行.get(键) != 值:
            符合条件 = False
            break
    if 符合条件:
        结果.append(行)
return 结果

def 统计金额(数据列表, *字段):
"""对指定数值字段求和"""
统计结果 = {}
for 字段名 in 字段:
    总计 = sum(int(行[字段名]) for 行 in 数据列表)
    统计结果[字段名] = 总计
return 统计结果

def 生成报告(标题, **元数据):
"""生成格式化报告"""
print(f"\n{'='*50}")
print(f"📊 {标题}")
print(f"{'='*50}")
for 键, 值 in 元数据.items():
    print(f"  {键}:{值}")
print(f"{'='*50}\n")

# 读取数据
数据 = 读取CSV("销售数据.csv")
print(f"📂 共读取 {len(数据)} 条数据")

# 场景1:筛选 iPhone 销售记录
iphone销售 = 筛选数据(数据, 商品="iPhone")
print(f"\n📱 iPhone 销售记录:{len(iphone销售)} 条")

# 场景2:统计总销售额
统计 = 统计金额(数据, "数量", "单价")
总收入 = 统计["数量"] * 统计["单价"]  # 简化计算,实际应该逐行计算
print(f"💰 总销量:{统计['数量']} 件")
print(f"💵 总库存价值(单价*数量):约 {总收入:,} 元")

# 场景3:生成自定义报告(用关键字参数)
生成报告(
"2024年1月销售汇总",
总订单数=len(数据),
iPhone订单数=len(iphone销售),
热销商品="iPhone",
平均客单价=统计["单价"] // len(数据)
)

输出:

📂 共读取 5 条数据

📱 iPhone 销售记录:2 条

💰 总销量:19 件
💵 总库存价值(单价*数量):约 79,941 元

==================================================
📊 2024年1月销售汇总
==================================================
总订单数:5
iPhone订单数:2
热销商品:iPhone
平均客单价:5999
==================================================

一句话解释**筛选条件 让函数可以接受「任意数量的筛选条件」,**元数据 让报告内容可扩展。

配图2 - 配图2


项目 3:天气查询小工具(15 分钟)

需求:组合前面的知识,做一个能查询多个城市天气、并输出对比报告的工具。

"""天气查询工具 - 综合实战"""

from datetime import datetime

# 模拟天气数据(实际项目中会调用真实 API)
模拟数据库 = {
"北京": {"温度": -2, "天气": "晴", "风力": "3级", "PM2.5": 45},
"上海": {"温度": 8, "天气": "多云", "风力": "2级", "PM2.5": 78},
"广州": {"温度": 18, "天气": "小雨", "风力": "4级", "PM2.5": 32},
"深圳": {"温度": 20, "天气": "阴", "风力": "2级", "PM2.5": 55},
"成都": {"温度": 12, "天气": "雾", "风力": "1级", "PM2.5": 120},
}

def 查询天气(城市, 风力=None, **扩展信息):
"""查询单个城市的天气,支持额外信息扩展"""
if 城市 not in 模拟数据库:
    return {"错误": f"未找到城市:{城市}"}

数据 = 模拟数据库[城市].copy()
if 风力:
    数据["风力"] = 风力  # 可以覆盖原数据
数据.update(扩展信息)    # 可以添加新字段
return 数据

def 批量查询(*城市列表, 日期=None):
"""批量查询多个城市的天气"""
if 日期 is None:
    日期 = datetime.now().strftime("%Y-%m-%d")

结果 = {}
for 城市 in 城市列表:
    天气数据 = 查询天气(城市)
    结果[城市] = 天气数据
return {"查询日期": 日期, "城市数量": len(城市列表), "数据": 结果}

def 生成天气报告(查询结果):
"""生成美观的天气对比报告"""
print(f"\n{'🏙️ '*20}")
print(f"📅 {查询结果['查询日期']} 天气报告")
print(f"共查询 {查询结果['城市数量']} 个城市\n")

城市数据 = 查询结果["数据"]
for 城市, 天气 in 城市数据.items():
    if "错误" in 天气:
        print(f"  ❌ {城市}:{天气['错误']}")
    else:
        print(f"  🌆 {城市}")
        print(f"     温度:{天气['温度']}°C | 天气:{天气['天气']}")
        print(f"     风力:{天气['风力']} | PM2.5:{天气['PM2.5']}")
print(f"\n{'🏙️ '*20}")

# ========== 演示 ==========

# 查询单个城市
北京天气 = 查询天气("北京")
print("单个城市查询:", 北京天气)

# 查询多个城市(演示 *args)
结果 = 批量查询("北京", "上海", "广州", "深圳", 日期="2024-01-15")
生成天气报告(结果)

# 演示扩展字段
带提示 = 查询天气("成都", PM2.5浓度=150, 建议="减少户外活动")
print(f"\n🆕 扩展字段演示:{带提示}")

输出:

单个城市查询:{'温度': -2, '天气': '晴', '风力': '3级', 'PM2.5': 45}

🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 
📅 2024-01-15 天气报告
共查询 4 个城市

🌆 北京
 温度:-2°C | 天气:晴
 风力:3级 | PM2.5:45
🌆 上海
 温度:8°C | 天气:多云
 风力:2级 | PM2.5:78
🌆 广州
 温度:18°C | 天气:小雨
 风力:4级 | PM2.5:32
🌆 深圳
 温度:20°C | 天气:阴
 风力:2级 | PM2.5:55

🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 🏙️ 

🆕 扩展字段演示:{'温度': 12, '天气': '雾', '风力': '1级', 'PM2.5': 120, 'PM2.5浓度': 150, '建议': '减少户外活动'}

一句话解释批量查询(*城市列表) 让用户可以传 1 个、3 个或 10 个城市,函数都能处理。


💪 进阶 20 分钟:常见坑 + 性能小贴士

坑 1:默认参数千万别用可变对象!

❌ 错误示例:

def 添加商品(商品列表=[], 商品名=""):
商品列表.append(商品名)
return 商品列表

print(添加商品(商品名="iPhone"))   # ['iPhone']
print(添加商品(商品名="MacBook"))  # ['iPhone', 'MacBook'] ← 保留上次的结果!

原因:Python 函数的默认参数在函数定义时只创建一次,可变对象会被所有调用共享。

✅ 正确写法:

def 添加商品(商品列表=None, 商品名=""):
if 商品列表 is None:
    商品列表 = []  # 每次调用创建新列表
商品列表.append(商品名)
return 商品列表

print(添加商品(商品名="iPhone"))   # ['iPhone']
print(添加商品(商品名="MacBook"))  # ['MacBook'] ← 干净了

坑 2:位置参数和关键字参数混用时,顺序不能乱

❌ 错误示例:

def 介绍(姓名, 年龄, 城市):
print(f"{姓名},{年龄}岁,来自{城市}")

介绍(城市="北京", "张三", 25)  # SyntaxError

✅ 正确写法:位置参数必须在关键字参数前面

介绍("张三", 25, 城市="北京")  # ✅

坑 3:*args**kwargs 的顺序

❌ 错误示例:

def 函数(**kwargs, *args):  # SyntaxError
pass

✅ 正确顺序:*args 在前,**kwargs 在后

def 函数(*args, **kwargs):
pass

坑 4:解包时数量不匹配

def 三参数(a, b, c):
print(f"a={a}, b={b}, c={c}")

数据 = [1, 2]
三参数(*数据)  # TypeError: 需要3个参数,但给了2个

坑 5:关键字参数名不能重复

def 函数(a, b):
print(a, b)

函数(1, b=2, b=3)  # SyntaxError: 关键字参数重复

性能小贴士:避免不必要的参数拷贝

# 如果函数不需要修改参数,尽量不要拷贝
def 求和_高效(*数字):
return sum(数字)  # 直接用,不拷贝

# 而不是:
def 求和_低效(*数字):
副本 = list(数字)  # 多余的拷贝
return sum(副本)

调试技巧:用 inspect 模块查看函数签名

import inspect

def 示例函数(a, b=10, *args, c, **kwargs):
pass

sig = inspect.signature(示例函数)
print(f"参数:{sig}")
print(f"参数列表:{list(sig.parameters.keys())}")

输出:

参数:(a, b=10, *args, c, **kwargs)
参数列表:['a', 'b', 'args', 'c', 'kwargs']

✏️ 练习题

练习 1(2 分钟):默认参数的魅力

  • 输入:调用 计算面积(5)计算面积(5, 3),后者计算矩形面积
  • 预期输出
圆形面积 = 78.54
矩形面积 = 15
  • 提示:写一个函数,默认参数 形状="圆形",圆形公式 πr²,矩形公式 长×宽

练习 2(2 分钟):给项目 1 加个判断

  • 输入:在 添加待办 函数里加一个判断,如果优先级是「紧急」,打印「⚡ 优先处理!」
  • 预期输出:添加优先级为「紧急」的待办时多一行 ⚡ 优先处理!
  • 提示:用 if 优先级 == "紧急": 判断

练习 3(3 分钟):用项目 2 的方法处理新数据

  • 输入:处理以下成绩数据,筛选出数学成绩 > 90 的学生:
成绩表 = [
  {"姓名": "小明", "数学": 95, "语文": 88},
  {"姓名": "小红", "数学": 87, "语文": 92},
  {"姓名": "小刚", "数学": 91, "语文": 85},
]
  • 预期输出{'姓名': '小明', '数学': 95, '语文': 88}{'姓名': '小刚', '数学': 91, '语文': 85}
  • 提示:复用项目 2 的 筛选数据 函数,**筛选条件数学="91" 之类的

练习 4(3 分钟):组合项目 2 和项目 3

  • 输入:写一个函数,接收 *城市**筛选条件,查询满足筛选条件的城市
  • 预期输出:筛选出温度 > 15°C 的城市
  • 提示:把项目 2 的筛选逻辑和项目 3 的批量查询组合起来

练习 5(5 分钟):分析报错原因

  • 输入:运行以下代码会报错:
def 测试(a, b, c=10, *args, d, e=5, **kwargs):
  print(a, b, c, args, d, e, kwargs)

测试(1, 2, 3, 4, 5, d=6, f=7)
  • 问题:输出是什么?哪个参数拿到了哪个值?为什么?
  • 提示:手动画一画参数流向

作业:做一个「命令行个人信息管理工具」

需求描述:做一个类似项目 1 的工具,但管理的是个人信息(姓名、年龄、职业、联系方式等)。

功能点
1. 添加联系人(支持默认参数:职业默认「未知」,城市默认「未填写」)
2. 批量添加联系人(用 *args
3. 按条件筛选联系人(用 **kwargs,支持任意字段的筛选)
4. 输出联系人报告(格式化打印)

加分项
1. 支持删除和更新联系人
2. 数据保存到本地 JSON 文件,下次启动自动加载

验收标准
- 能跑起来,不报错
- 至少测试 3 种添加方式和 2 种筛选方式
- 代码有注释,说明每种参数的作用


📚 总结 + 资源

一句话总结:今天学了 4 种参数(默认/位置/关键字/不定),能让你的函数更灵活、更易用、更健壮。

延伸学习
- Python 官方文档:函数定义 — 官方权威解释
- 《Python 编程:从入门到实践》第 3 章 — 实战导向的教材
- 慕课网「Python 函数式编程」视频课 — 深入理解参数设计

互动钩子:你在工作中有没有遇到过「函数参数太多,不知道怎么传」的尴尬?或者自己写过什么巧妙的函数设计?评论区聊聊,老粉优先回复!


📌 下章预告:学会了函数的参数,下一章我们要解决一个新问题——函数内部的变量「能看到吗」?修改会不会影响外部?这就涉及到「作用域」和「闭包」的概念了……

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。