飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

解包实参

调用函数时,如果实参已经存放在序列或字典中,可以用 * 和 ** 操作符将其"解包"为独立参数。解包是可变参数的逆操作:*args 在定义时收集参数,* 在调用时展开参数。

* 序列解包

当函数需要多个独立位置参数,但你的数据在一个列表或元组中时,用 * 解包:

def add_three(a, b, c):
    return a + b + c

nums = [1, 2, 3]
print(add_three(*nums))   # 6,等价于 add_three(1, 2, 3)

* 可以作用于任何可迭代对象:列表、元组、字符串、range、生成器等:

print(add_three(*range(3)))        # 0 + 1 + 2 = 3
print(add_three(*"abc"))            # abc(字符串解包为 'a', 'b', 'c')
print(add_three(*{1, 2, 3}))        # 6(集合无序,但元素是 1, 2, 3)

注意:序列长度必须与形参数量匹配,否则会报错:

# add_three(*[1, 2])       # TypeError: add_three() missing 1 required positional argument
# add_three(*[1, 2, 3, 4]) # TypeError: add_three() takes 3 positional arguments but 4 were given

** 字典解包

当函数需要多个关键字参数,但你的数据在一个字典中时,用 ** 解包:

def describe(name, age, city):
    return f"{name},{age}岁,住在{city}"

info = {"name": "Alice", "age": 30, "city": "北京"}
print(describe(**info))
# Alice,30岁,住在北京

** 要求字典的键必须是字符串,且与函数形参名匹配。多余的键如果没有对应的 **kwargs 会报错:

# describe(**{"name": "Bob", "age": 25, "city": "上海", "extra": 1})
# TypeError: describe() got an unexpected keyword argument 'extra'

混合解包

* 和 ** 可以在同一次调用中混用,也可以与普通实参混用:

def func(a, b, c, d=4, e=5):
    return (a, b, c, d, e)

args = [1, 2]
kwargs = {"d": 40, "e": 50}

print(func(*args, 3, **kwargs))   # (1, 2, 3, 40, 50)

混用时的规则与常规调用一致:位置参数在前,关键字参数在后。解包的位置参数视为位置参数,解包的关键字参数视为关键字参数。

# 合法:位置解包在前,普通位置在中,关键字解包在后
print(func(*[1, 2], 3, **{"d": 40}))

# 非法:关键字解包后接位置参数
# print(func(**{"d": 40}, 1, 2, 3))   # SyntaxError

与可变参数的对应关系

定义时的 *args 和调用时的 * 互为逆操作。定义时的 **kwargs 和调用时的 ** 互为逆操作:

def collect(*args, **kwargs):
    return args, kwargs

# 正向:传入独立参数,收集为元组和字典
collected = collect(1, 2, 3, x=10, y=20)
print(collected)
# ((1, 2, 3), {'x': 10, 'y': 20})

# 逆向:用 * 和 ** 把元组和字典展开
args = (1, 2, 3)
kwargs = {"x": 10, "y": 20}
print(collect(*args, **kwargs))
# ((1, 2, 3), {'x': 10, 'y': 20})

这种对称性在函数包装和委托中非常有用。例如,写一个通用的日志包装器:

def logged(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数: {args}, {kwargs}")
        return func(*args, **kwargs)   # 原样解包转发
    return wrapper

@logged
def add(a, b):
    return a + b

print(add(2, 3))
# 调用 add,参数: (2, 3), {}
# 5

解包与仅位置参数的交互

当函数有仅位置参数(/ 前)时,解包的位置参数仍然可以正常匹配:

def only_pos(a, b, /, c):
    return a + b + c

print(only_pos(*[1, 2], 3))       # 6
print(only_pos(*[1, 2, 3]))       # 6

但仅位置参数不能用关键字形式传入,即使通过字典解包也不行:

# only_pos(**{"a": 1, "b": 2, "c": 3})
# TypeError: only_pos() got some positional-only arguments passed as keyword arguments: 'a, b'

多重解包(Python 3.5+)

从 Python 3.5 开始,调用时可以使用多个 * 解包:

def sum_all(a, b, c, d, e):
    return a + b + c + d + e

front = [1, 2]
back = [4, 5]
print(sum_all(*front, 3, *back))   # 15

同样,多个 ** 解包也允许,但后面的字典会覆盖前面的同名键:

def show(a, b):
    return f"a={a}, b={b}"

d1 = {"a": 1, "b": 2}
d2 = {"b": 20}
print(show(**d1, **d2))   # a=1, b=20

常见错误

对不可迭代对象使用 *:

# add_three(*42)   # TypeError: 'int' object is not iterable

字典键不是字符串:

# describe(**{1: "a"})   # TypeError: keywords must be strings

解包后参数重复:

# describe("Alice", **{"name": "Bob"})
# TypeError: describe() got multiple values for argument 'name'

小结

* 把可迭代对象解包为独立位置参数,** 把字典解包为独立关键字参数。两者可以混用、多次使用,与可变参数 *args/**kwargs 形成完美的对称。掌握解包机制,是编写灵活函数调用和包装器的关键。

上一页
可变参数
下一页
函数返回值