02-函数参数详解
Python 版本要求:Python 3.11+ 贯穿项目:functions_demo/ 代码位置:
app/core/functions.py(create_student) 测试验证:cd functions_demo && uv run pytest -k TestFunctionArgs -v
本章代码基于 Python 3.11+ 编写
掌握 Python 函数的各种参数类型,让函数调用更灵活。
概念铺垫
为什么需要多种参数类型?一个真实的配置场景
问题场景: 你在开发一个发送邮件的函数,不同场景需要不同的参数。
固定参数的局限:
python
def send_email(to: str, subject: str, body: str) -> None:
"""发送邮件"""
print(f"发送到:{to}")
print(f"主题:{subject}")
print(f"内容:{body}")
# 每次都要传所有参数
send_email("user@example.com", "通知", "你好!")
send_email("admin@example.com", "警告", "系统异常!")
send_email("team@example.com", "报告", "周报已提交")问题:
- 很多时候主题都是"通知",每次都要重复写
- 能否让某些参数有默认值?
- 如果参数很多,顺序容易搞混
灵活参数的解决方案:
python
def send_email(
to: str,
subject: str = "通知",
body: str = "",
cc: list[str] | None = None,
priority: str = "normal"
) -> None:
"""发送邮件(带默认参数)"""
cc_list = cc or []
print(f"发送到:{to}")
print(f"主题:{subject}")
print(f"内容:{body}")
print(f"抄送:{cc_list}")
print(f"优先级:{priority}")
# 简化调用
send_email("user@example.com") # 使用默认主题和内容
send_email("admin@example.com", "警告") # 自定义主题
send_email("team@example.com", "报告", "周报", ["boss@example.com"]) # 完整参数这就是参数灵活性的价值:让函数调用更简洁、更易读。
参数类型解决了什么问题?
不同的参数类型解决不同的问题:
| 参数类型 | 解决的问题 | 示例 |
|---|---|---|
| 位置参数 | 固定顺序的必需参数 | func(a, b) |
| 默认参数 | 可选参数,减少重复 | func(a, b=1) |
| 关键字参数 | 打破顺序限制 | func(b=2, a=1) |
| *args | 任意数量的位置参数 | func(1, 2, 3, ...) |
| **kwargs | 任意数量的关键字参数 | func(x=1, y=2, ...) |
L1 理解层:会用
参数的最简用法
python
# 位置参数(按顺序传)
def greet(name: str, age: int) -> None:
print(f"{name},{age} 岁")
greet("张三", 25) # 按顺序传递
# 默认参数(可省略)
def greet_with_default(name: str, greeting: str = "你好") -> None:
print(f"{greeting},{name}!")
greet_with_default("张三") # 你好,张三!
greet_with_default("张三", "欢迎") # 欢迎,张三!
# 关键字参数(按名称传)
greet(age=25, name="张三") # 顺序随意第一部分:位置参数
概念说明
调用函数时,按照参数定义的顺序依次传入值,这就是位置参数。
python
def describe_pet(animal_type: str, pet_name: str) -> None:
"""描述宠物"""
print(f"我有一只{animal_type},它叫{pet_name}")
describe_pet("狗", "旺财") # 我有一只狗,它叫旺财
describe_pet("猫", "咪咪") # 我有一只猫,它叫咪咪
# ❌ 顺序颠倒:逻辑错误(不会报错,但结果不对)
describe_pet("旺财", "狗") # 我有一只旺财,它叫狗第二部分:默认参数
概念说明
给参数提前设好默认值,调用时如果不提供,就用默认值。
python
def greet(name: str, greeting: str = "你好") -> None:
"""打招呼,greeting 有默认值"""
print(f"{greeting},{name}!")
greet("Alice") # 你好,Alice! ← 用默认值
greet("Bob", "Hello") # Hello,Bob! ← 覆盖默认值⚠️ 陷阱:可变对象做默认参数
python
# ❌ 危险写法:默认参数只创建一次,被所有调用共享!
def add_item(item: int, items: list[int] = []) -> list[int]:
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 期望是 [2],结果混入了上次的数据
# ✅ 正确写法:用 None 作占位符
def add_item(item: int, items: list[int] | None = None) -> list[int]:
if items is None:
items = []
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [2] ← 每次都是新列表┌─────────────────────────────────────────────────────────────┐
│ 默认参数陷阱解释 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 默认参数在函数定义时创建,不是每次调用时创建 │
│ │
│ ❌ 错误理解: │
│ 每次调用 add_item(),items=[] 都重新创建 │
│ │
│ ✅ 正确理解: │
│ items=[] 在定义函数时就创建了 │
│ 所有调用共享同一个列表对象 │
│ │
│ 解决方案: │
│ 用 None 作为默认值,在函数内部创建新对象 │
│ │
└─────────────────────────────────────────────────────────────┘第三部分:关键字参数
概念说明
调用时用 参数名=值 的形式传递,顺序可以随意,代码可读性更高。
python
def describe_person(name: str, age: int, city: str) -> None:
print(f"{name},{age} 岁,住在 {city}")
# 位置方式(顺序严格)
describe_person("张三", 25, "北京")
# 关键字方式(顺序随意)
describe_person(city="北京", name="张三", age=25)
# 混合使用(位置参数必须在关键字参数前面)
describe_person("张三", age=25, city="北京")第四部分:*args — 可变位置参数
概念说明
*args 让函数接受任意数量的位置参数。函数内部 args 是一个元组。
python
def sum_all(*args: int) -> int:
"""计算任意个数的和"""
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all(10)) # 10
print(sum_all()) # 0
# 查看 args 的类型
def show(*args: int | str) -> None:
print(type(args), args)
show(1, "hello", True) # <class 'tuple'> (1, 'hello', True)解包传入
python
def sum_all(*args: int) -> int:
return sum(args)
numbers = [1, 2, 3, 4, 5]
# 用 * 解包列表传入
print(sum_all(*numbers)) # 15
# 等价于
print(sum_all(1, 2, 3, 4, 5)) # 15第五部分:**kwargs — 可变关键字参数
概念说明
**kwargs 让函数接受任意数量的关键字参数。函数内部 kwargs 是一个字典。
python
def print_info(**kwargs: str | int) -> None:
"""打印所有传入的键值对"""
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="Beijing")
# name: Alice
# age: 25
# city: Beijing
# 查看 kwargs 的类型
def show(**kwargs: int) -> None:
print(type(kwargs), kwargs)
show(x=1, y=2) # <class 'dict'> {'x': 1, 'y': 2}解包传入
python
def print_info(**kwargs: str) -> None:
for k, v in kwargs.items():
print(f"{k}: {v}")
data = {"name": "Alice", "city": "Beijing"}
# 用 ** 解包字典传入
print_info(**data)
# name: Alice
# city: Beijing参数组合顺序
规则
┌─────────────────────────────────────────────────────────────┐
│ 参数定义顺序 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 正确顺序: │
│ 位置参数 → 默认参数 → *args → **kwargs │
│ │
│ def func(a, b, c=1, d=2, *args, **kwargs): │
│ pass │
│ │
│ ───────────────────────────────────────────────────── │
│ │
│ ❌ 错误顺序: │
│ def func(*args, a): # 位置参数在 *args 后 │
│ def func(a, **kwargs, b): # 位置参数在 **kwargs 后 │
│ │
└─────────────────────────────────────────────────────────────┘示例
python
def func(a: int, b: int, c: int = 10, *args: int, **kwargs: int) -> None:
print(f"a={a}, b={b}, c={c}")
print(f"args={args}")
print(f"kwargs={kwargs}")
func(1, 2)
# a=1, b=2, c=10
# args=()
# kwargs={}
func(1, 2, 3, 4, 5)
# a=1, b=2, c=3
# args=(4, 5)
# kwargs={}
func(1, 2, 3, 4, 5, x=100, y=200)
# a=1, b=2, c=3
# args=(4, 5)
# kwargs={'x': 100, 'y': 200}渐进复杂:从简单到复杂的参数
层级 1:纯位置参数
python
def add(a: int, b: int) -> int:
return a + b
add(1, 2)层级 2:添加默认参数
python
def greet(name: str, greeting: str = "你好") -> str:
return f"{greeting},{name}!"
greet("张三") # 你好,张三!
greet("张三", "欢迎") # 欢迎,张三!层级 3:混合位置和关键字
python
def create_user(name: str, age: int, city: str = "未知") -> dict:
return {"name": name, "age": age, "city": city}
create_user("张三", 25)
create_user("张三", 25, "北京")
create_user("张三", city="上海", age=30)层级 4:使用 *args
python
def calculate_stats(*scores: float) -> dict:
"""计算统计信息"""
return {
"count": len(scores),
"sum": sum(scores),
"avg": sum(scores) / len(scores) if scores else 0
}
calculate_stats(85, 90, 78, 92)
# {'count': 4, 'sum': 345, 'avg': 86.25}层级 5:完整参数组合
python
def process_data(
name: str,
*values: float,
unit: str = "分",
**options: bool
) -> dict:
"""
处理数据
Args:
name: 数据名称
*values: 数据值
unit: 单位
**options: 额外选项
Returns:
处理结果
"""
result = {
"name": name,
"values": list(values),
"unit": unit,
"count": len(values),
"sum": sum(values),
"avg": sum(values) / len(values) if values else 0
}
if options.get("detailed", False):
result["min"] = min(values) if values else 0
result["max"] = max(values) if values else 0
return result
# 使用
process_data("成绩", 85, 90, 78, unit="分", detailed=True)
# {'name': '成绩', 'values': [85, 90, 78], 'unit': '分',
# 'count': 3, 'sum': 253, 'avg': 84.33, 'min': 78, 'max': 90}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
*values: float | 接收任意数量的位置参数 | 用 *args 让调用者可传入 0 到 N 个数值,无需手动构造列表 |
unit: str = "分" | *values 之后的关键字专用参数 | 位于 *args 后面的参数只能通过关键字传入,语义清晰不易误传 |
**options: bool | 接收任意关键字参数 | 用 **kwargs 保留扩展空间,无需修改签名即可添加新选项 |
options.get("detailed", False) | 安全读取字典键 | get 不存在时返回默认值,避免 KeyError |
实际应用:灵活的配置函数
python
def create_student(
name: str, # 位置参数
score: float, # 位置参数
subject: str = "数学", # 默认参数
/, # 分隔符:以上只能位置传参
*tags: str, # *args: 额外标签
comment: str = "", # 仅关键字参数
rank: int | None = None,
) -> dict:
"""创建学生记录"""
return {
"name": name, "score": score, "subject": subject,
"tags": tags, "comment": comment, "rank": rank
}
# 基础调用
student1 = create_student("张三", 85)
print(student1)
# {'name': '张三', 'score': 85, 'subject': '数学',
# 'tags': (), 'comment': '', 'rank': None}
# 完整调用
student2 = create_student(
"李四", 92, "英语",
"努力", "进步大",
comment="表现优秀",
rank=1
)
print(student2)
# {'name': '李四', 'score': 92, 'subject': '英语',
# 'tags': ('努力', '进步大'), 'comment': '表现优秀', 'rank': 1}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
name: str, score: float, subject: str = "数学" | 位置参数 + 默认参数 | 必填项在前,可选项在后,符合参数定义顺序规范 |
/ | positional-only 分隔符 | Python 3.8+ 新特性,强制前三个参数只能按位置传入,防止误用关键字 |
*tags: str | 收集额外标签为元组 | 调用者可以传 0 到多个标签,比传 list 参数更简洁 |
| `comment: str = "", rank: int | None = None` | keyword-only 参数 |
L2 实践层:最佳实践
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
| 必填参数在前 | 调用时必须传的参数放在前面,减少遗漏 | def func(required, optional=1): |
| 用 None 作默认值 | 避免可变默认参数被多次调用污染 | def func(items: list | None = None): |
| 关键字参数提高可读性 | 多参数调用时用关键字,避免顺序混淆 | create_user(name="张三", age=25, city="北京") |
| *限制 args 使用 | 只在真正需要"任意数量"时用,不要滥用 | sum_all(1, 2, 3) 比 sum_all(*numbers) 直接 |
| kwargs 配合类型提示 | 用 TypedDict 或 Total 指定 kwargs 结构 | def func(**options: int): 加文档说明期望键 |
| 参数顺序规则 | 位置→默认→*args→**kwargs,违反会 SyntaxError | Python 强制要求此顺序 |
反模式:不要这样做
python
# ❌ 可变对象作为默认参数
def add_item(item: int, items: list[int] = []) -> list[int]:
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 期望 [2],结果混入上次的数据
# 问题:
# 1. 默认参数在函数定义时创建,不是每次调用时创建
# 2. 所有调用共享同一个列表对象
# 3. 这是一个经典的 Python 陷阱,被称为"可变默认参数陷阱"python
# ✅ 正确做法:用 None 作占位符
def add_item(item: int, items: list[int] | None = None) -> list[int]:
if items is None:
items = [] # 每次调用都创建新列表
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [2] ← 每次都是新列表python
# ❌ 参数顺序混乱
def send_email(to, subject, body, cc, bcc, priority, encoding, charset):
pass
# 调用时顺序容易搞错
send_email("user@example.com", "通知", "你好",
None, None, "high", "utf-8", "utf-8") # cc 和 bcc 是 None?python
# ✅ 正确做法:用关键字参数,或重新设计参数结构
def send_email(
to: str,
subject: str = "通知",
body: str = "",
*,
cc: list[str] | None = None,
bcc: list[str] | None = None,
priority: str = "normal",
encoding: str = "utf-8"
) -> None:
"""发送邮件
Args:
to: 收件人(必填)
subject: 主题
body: 正文
cc: 抄送列表(关键字专用)
bcc: 密送列表(关键字专用)
priority: 优先级
encoding: 编码
"""
pass
# * 之后必须用关键字参数,强制语义清晰
send_email("user@example.com", cc=["boss@example.com"], priority="high")python
# ❌ 过度使用 *args
def process(*args):
"""处理任意数量的参数"""
# 问题:调用者不知道应该传什么
pass
process(1, 2, 3, "hello", True, [1, 2]) # 什么都能传,但函数内部怎么处理?python
# ✅ 正确做法:明确参数或用结构化数据
def process_numbers(numbers: list[int]) -> int:
"""处理数字列表"""
return sum(numbers)
# 或用 TypedDict 定义结构
from typing import TypedDict
class ProcessInput(TypedDict):
values: list[int]
labels: list[str]
flags: dict[str, bool]
def process_structured(data: ProcessInput) -> dict:
"""处理结构化数据"""
return {"sum": sum(data["values"]), "labels": data["labels"]}python
# ❌ kwargs 无文档说明
def configure(**kwargs):
"""配置系统"""
for key, value in kwargs.items():
# 问题:调用者不知道哪些 key 是有效的
pass
configure(unknown_key="value") # 无效键,但函数不会报错python
# ✅ 正确做法:在 docstring 中说明 kwargs 结构
def configure(**kwargs: str | int | bool) -> None:
"""配置系统
Args:
**kwargs: 配置选项,支持以下键:
- debug: bool, 是否启用调试模式
- timeout: int, 超时时间(秒)
- log_level: str, 日志级别
其他键将被忽略
"""
valid_keys = {"debug", "timeout", "log_level"}
for key, value in kwargs.items():
if key in valid_keys:
# 处理有效配置
pass
else:
print(f"警告:未知配置项 {key}")
configure(debug=True, timeout=30, unknown_key="ignored")
# 警告:未知配置项 unknown_keypython
# ❌ 参数顺序错误
def func(*args, a, b): # SyntaxError!
pass
def func(a, **kwargs, b): # SyntaxError!
passpython
# ✅ 正确做法:按规则排列
def func(a: int, b: int, *args: int, **kwargs: int) -> None:
"""正确顺序:位置参数 → 默认参数 → *args → **kwargs"""
pass
# 或使用 * 强制关键字参数
def func(a: int, *args: int, b: int, **kwargs: int) -> None:
"""*args 之后必须用关键字参数"""
pass
func(1, 2, 3, b=4, c=5) # a=1, args=(2, 3), b=4, kwargs={'c': 5}适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 简单必填参数 | ✅ 推荐 | 位置参数,顺序清晰 |
| 可选配置项 | ✅ 推荐 | 默认参数,减少调用负担 |
| API 接口函数 | ✅ 推荐 | 关键字参数,语义明确 |
| 回调函数 | ✅ 推荐 | 固定签名,调用者容易理解 |
| 包装其他函数 | ✅ 推荐 | *args/**kwargs 透传参数 |
| 工具函数 | ❓ 看情况 | 参数数量不确定时用 *args |
| 配置加载 | ❓ 看情况 | kwargs 配合验证函数 |
| 入口函数 | ❌ 不推荐 | 参数太灵活难以维护 |
L3 专家层:底层原理
Python 如何实现参数传递
Python 参数传递的核心机制是对象引用传递,所有参数都是引用,但根据对象类型(可变/不可变)表现不同。
参数传递机制:
┌─────────────────────────────────────────────────────────────┐
│ Python 参数传递原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 所有参数都是"对象引用"传递 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 不可变对象(int, str, tuple) │ │
│ │ │ │
│ │ 函数内部重新赋值 → 创建新对象 │ │
│ │ 原对象不变 │ │
│ │ │ │
│ │ def func(x): │ │
│ │ x = 10 # 创建新 int 对象 │ │
│ │ # 外部的 x 不受影响 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 可变对象(list, dict) │ │
│ │ │ │
│ │ 函数内部修改 → 直接修改原对象 │ │
│ │ 外部可见变化 │ │
│ │ │ │
│ │ def func(lst): │ │
│ │ lst.append(1) # 修改原列表 │ │
│ │ # 外部的 lst 也被修改 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘python
# 验证参数传递机制
def modify_immutable(x: int) -> int:
"""修改不可变对象"""
print(f"函数内 x 的 id: {id(x)}")
x = 10 # 创建新对象
print(f"赋值后 x 的 id: {id(x)}")
return x
def modify_mutable(lst: list[int]) -> None:
"""修改可变对象"""
print(f"函数内 lst 的 id: {id(lst)}")
lst.append(1) # 修改原对象
print(f"append 后 lst 的 id: {id(lst)}") # id 不变
# 测试不可变对象
a = 5
print(f"外部 a 的 id: {id(a)}")
modify_immutable(a)
print(f"调用后 a: {a}") # 5(不变)
# 测试可变对象
b = []
print(f"外部 b 的 id: {id(b)}")
modify_mutable(b)
print(f"调用后 b: {b}") # [1](已修改)*args 和 **kwargs 的内部实现
*args / **kwargs 内部机制:
┌─────────────────────────────────────────────────────────────┐
│ 可变参数的打包与解包 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 调用时打包: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ func(1, 2, 3, a=4, b=5) │ │
│ │ │ │
│ │ 内部转换: │ │
│ │ args = (1, 2, 3) → 元组 │ │
│ │ kwargs = {'a': 4, 'b': 5} → 字典 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 传参时解包: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ numbers = [1, 2, 3] │ │
│ │ func(*numbers) │ │
│ │ │ │
│ │ 内部转换: │ │
│ │ func(1, 2, 3) → 展开为位置参数 │ │
│ │ │ │
│ │ options = {'x': 1, 'y': 2} │ │
│ │ func(**options) │ │
│ │ │ │
│ │ 内部转换: │ │
│ │ func(x=1, y=2) → 展开为关键字参数 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘python
# 查看 *args 和 **kwargs 的内部结构
def show_args(*args: int, **kwargs: str) -> None:
"""演示 args 和 kwargs 的内部结构"""
print(f"args 类型: {type(args)}, 值: {args}")
print(f"kwargs 类型: {type(kwargs)}, 值: {kwargs}")
show_args(1, 2, 3, a="hello", b="world")
# args 类型: <class 'tuple'>, 值: (1, 2, 3)
# kwargs 类型: <class 'dict'>, 值: {'a': 'hello', 'b': 'world'}
# 解包演示
numbers = [1, 2, 3]
options = {"x": "a", "y": "b"}
show_args(*numbers, **options)
# args 类型: <class 'tuple'>, 值: (1, 2, 3)
# kwargs 类型: <class 'dict'>, 值: {'x': 'a', 'y': 'b'}函数签名对象
Python 使用 inspect 模块来解析函数签名,这是参数处理的底层实现。
python
import inspect
def example_func(a: int, b: int = 10, *args: int, c: str = "default", **kwargs: str) -> str:
"""示例函数"""
return f"a={a}, b={b}, args={args}, c={c}, kwargs={kwargs}"
# 获取函数签名
sig = inspect.signature(example_func)
print(sig) # (a: int, b: int = 10, *args: int, c: str = 'default', **kwargs: str) -> str
# 分析参数
for name, param in sig.parameters.items():
print(f"{name}: {param.kind.name}, default={param.default}")
# a: POSITIONAL_OR_KEYWORD, default=<class 'inspect._empty'>
# b: POSITIONAL_OR_KEYWORD, default=10
# args: VAR_POSITIONAL, default=<class 'inspect._empty'>
# c: KEYWORD_ONLY, default='default'
# kwargs: VAR_KEYWORD, default=<class 'inspect._empty'>参数类型枚举:
| 类型 | 名称 | 说明 |
|---|---|---|
POSITIONAL_ONLY | 仅位置参数 | Python 不支持(用 / 语法,3.8+) |
POSITIONAL_OR_KEYWORD | 位置或关键字 | 普通参数 |
VAR_POSITIONAL | 可变位置 | *args |
KEYWORD_ONLY | 仅关键字 | * 后的参数 |
VAR_KEYWORD | 可变关键字 | **kwargs |
性能考量
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 位置参数传递 | O(1) | 直接赋值到局部变量数组 |
| *args 打包 | O(n) | 创建元组,n 为参数数量 |
| **kwargs 打包 | O(n) | 创建字典,n 为键数量 |
| 参数解包 * | O(n) | 展开序列为位置参数 |
| 参数解包 ** | O(n) | 展开字典为关键字参数 |
性能测试:
python
import timeit
# 直接传参
def direct_func(a: int, b: int, c: int) -> int:
return a + b + c
# *args
def args_func(*args: int) -> int:
return sum(args)
# **kwargs
def kwargs_func(**kwargs: int) -> int:
return sum(kwargs.values())
# 测试
direct_time = timeit.timeit("direct_func(1, 2, 3)", globals=globals(), number=1000000)
args_time = timeit.timeit("args_func(1, 2, 3)", globals=globals(), number=1000000)
kwargs_time = timeit.timeit("kwargs_func(a=1, b=2, c=3)", globals=globals(), number=1000000)
print(f"直接传参: {direct_time:.4f}s")
print(f"*args: {args_time:.4f}s")
print(f"**kwargs: {kwargs_time:.4f}s")
# 结果示例:
# 直接传参: 0.08s
# *args: 0.12s ← 约慢 50%(需要创建元组)
# **kwargs: 0.25s ← 约慢 3 倍(需要创建字典)优化建议:
python
# ❌ 频繁使用 *args/**kwargs(热点代码)
def process_many(*args):
return sum(args)
for _ in range(1000000):
process_many(1, 2, 3, 4, 5) # 每次都创建元组
# ✅ 热点代码用固定参数
def process_five(a: int, b: int, c: int, d: int, e: int) -> int:
return a + b + c + d + e
for _ in range(1000000):
process_five(1, 2, 3, 4, 5) # 无额外开销设计动机
Python 为什么这样设计参数系统?
| 设计选择 | 原因 | 替代方案对比 |
|---|---|---|
| 可选默认参数 | 减少调用负担,常见值预设 | Java 必须传所有参数或重载 |
| *args/**kwargs | 灵活包装,透传参数 | C 固定签名,需要 variadic 函数 |
| 关键字参数 | 语义清晰,顺序无关 | JavaScript 用对象传参 |
| 参数顺序规则 | 避免歧义,编译检查 | Perl 参数混乱 |
| 仅关键字参数(*) | 强制语义,避免错误传参 | Ruby 默认位置参数 |
为什么默认参数在定义时创建?
python
# 设计动机:性能优化
# 如果每次调用都创建默认值,会产生额外开销
def func(items: list[int] = []):
items.append(1)
# 如果每次调用创建新 list:
# func() → 创建 list [1]
# func() → 创建 list [1]
# 每次都有内存分配开销
# Python 选择在定义时创建一次:
# func() → 使用同一个 list
# 性能更好,但带来了"可变默认参数陷阱"
# 解决方案:用 None + 函数内创建
# 兼顾性能和安全知识关联
参数传递知识关联:
┌───────────────┐
│ inspect │
│ 函数签名 │
└───────────────┘
│
↓
┌─────────────┐ ┌───────────────┐ ┌───────────────┐
│ 对象引用 │────→│ 参数传递 │────→│ 打包/解包 │
│ 可变/不可变│ │ 机制 │ │ *args/**kwargs│
└─────────────┘ └───────────────┘ └───────────────┘
│
↓
┌───────────────┐
│ 默认参数 │
│ 创建时机 │
└───────────────┘
│
↓
┌───────────────┐
│ 装饰器 │
│ 参数透传 │
└───────────────┘本章小结
┌─────────────────────────────────────────────────────────────┐
│ 函数参数 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ L1 理解层: │
│ ✓ 位置参数:按顺序传递 │
│ ✓ 默认参数:有预设值,可省略 │
│ ✓ 关键字参数:按名称传递,顺序随意 │
│ ✓ *args:接收任意数量位置参数(元组) │
│ ✓ **kwargs:接收任意数量关键字参数(字典) │
│ │
│ L2 实践层: │
│ ✓ 用 None 作默认值,避免可变对象陷阱 │
│ ✓ 参数顺序:位置→默认→*args→**kwargs │
│ ✓ 多参数调用用关键字参数 │
│ ✓ kwargs 要有文档说明有效键 │
│ │
│ L3 专家层: │
│ ✓ 参数传递是对象引用传递 │
│ ✓ 不可变对象赋值创建新对象,可变对象直接修改 │
│ ✓ *args 打包为元组,**kwargs 打包为字典 │
│ ✓ 默认参数在定义时创建(性能优化) │
│ ✓ inspect.signature() 可解析函数签名 │
│ │
└─────────────────────────────────────────────────────────────┘交互演示
运行项目 CLI 查看本章代码的实际执行效果:
bash
cd functions_demo && uv run python -m app # 选 2