Skip to content

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: intNone = 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,违反会 SyntaxErrorPython 强制要求此顺序

反模式:不要这样做

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_key
python
# ❌ 参数顺序错误
def func(*args, a, b):  # SyntaxError!
    pass

def func(a, **kwargs, b):  # SyntaxError!
    pass
python
# ✅ 正确做法:按规则排列
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