源本科技 | 码上会

Python 的 *args 与 **kwargs

2026/01/16
6
0

学习目标

  • 理解 *args**kwargs 的作用与语法

  • 掌握如何使用 *args 接收任意数量的位置参数

  • 掌握如何使用 **kwargs 接收任意数量的关键字参数

  • 能够在同一个函数中同时使用 *args**kwargs

  • 了解参数解包(unpacking)的实际应用场景

  • 遵循命名规范,提升代码可读性与灵活性


为什么需要 *args 和 **kwargs

在实际开发中,我们常常遇到参数数量不确定的场景:

  • 日志记录函数可能接收任意数量的消息

  • 配置函数可能接受任意键值对

  • 工具函数需兼容不同调用方式

Python 提供了 *args**kwargs 机制,使函数能够灵活处理可变数量的输入。

名称说明:argskwargs 只是约定俗成的名称,实际可使用任意合法标识符(如 *values, **options),但强烈建议遵循惯例。


*args

接收任意位置参数

*args 将所有位置参数收集到一个元组中,可在函数内部遍历或处理。

基本用法

def greet(*args):
    for name in args:
        print(f"你好, {name}!")

greet("张三", "李四", "王五")

输出:

你好, 张三!
你好, 李四!
你好, 王五!

实用示例

计算任意数量数字的乘积

def multiply(*numbers):
    result = 1
    for num in numbers:
        result *= num
    return result

print(multiply(2, 3, 4))      # 24
print(multiply(5, 6))         # 30
print(multiply(10))           # 10

*args 本质是一个元组,支持索引、切片、长度查询等操作。


**kwargs

接收任意关键字参数

**kwargs 将所有关键字参数收集到一个字典中,键为参数名,值为传入的值。

基本用法

def describe(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

describe(name="赵六", age=28, job="工程师")

输出:

name: 赵六
age: 28
job: 工程师

实用示例

生成用户信息字符串

def format_user_info(**info):
    parts = []
    for field, value in info.items():
        parts.append(f"{field}: {value}")
    return " | ".join(parts)

user_str = format_user_info(
    姓名="钱七",
    年龄=32,
    邮箱="qianqi@example.com"
)
print(user_str)

输出:

姓名: 钱七 | 年龄: 32 | 邮箱: qianqi@example.com

同时使用

当函数需要同时处理位置参数和关键字参数时,可将两者结合使用。

顺序要求*args 必须出现在 **kwargs 之前

def log_event(*messages, **metadata):
    print("消息内容:", messages)
    print("附加信息:", metadata)

log_event(
    "用户登录", "IP 地址异常",
    user_id=1001,
    timestamp="2026-01-16T17:55:00",
    level="warning"
)

输出:

消息内容: ('用户登录', 'IP 地址异常')
附加信息: {'user_id': 1001, 'timestamp': '2026-01-16T17:55:00', 'level': 'warning'}

参数解包:反向使用 * 和 **

*** 不仅可用于函数定义,还可用于函数调用时解包已有数据结构。

解包列表/元组作为位置参数

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

numbers = [10, 20, 30]
result = add(*numbers)  # 等价于 add(10, 20, 30)
print(result)  # 60

解包字典作为关键字参数

def create_profile(name, age, city):
    return f"{name}, {age} 岁, 来自 {city}"

info = {"name": "孙八", "age": 26, "city": "杭州"}
profile = create_profile(**info)
print(profile)  # 孙八, 26 岁, 来自 杭州

这在动态调用函数或转发参数时非常有用。


完整函数签名中的参数顺序

当函数包含多种参数类型时,必须遵循以下顺序:

def func(positional, default=value, *args, **kwargs):
    pass

即:

  1. 普通位置参数

  2. 默认参数(关键字参数)

  3. *args

  4. **kwargs

def process_data(operation, verbose=True, *files, **options):
    print(f"操作: {operation}")
    print(f"详细模式: {verbose}")
    print(f"文件列表: {files}")
    print(f"选项: {options}")

process_data(
    "压缩",
    True,
    "file1.txt", "file2.log",
    algorithm="gzip", level=9
)

输出:

操作: 压缩
详细模式: True
文件列表: ('file1.txt', 'file2.log')
选项: {'algorithm': 'gzip', 'level': 9}

常见应用场景

场景

说明

装饰器

转发任意参数给被装饰函数

日志函数

记录任意数量的消息和上下文

API 封装

接收用户传入的任意查询参数

工厂函数

根据不同参数创建不同对象

测试工具

断言函数接收任意预期值

装饰器示例

def debug(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数: args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"返回值: {result}")
        return result
    return wrapper

@debug
def calculate(x, y, op="add"):
    return x + y if op == "add" else x - y

calculate(5, 3, op="sub")

重点总结

  • *args 接收任意数量位置参数,打包为元组

  • **kwargs 接收任意数量关键字参数,打包为字典

  • 两者可同时使用,但 *args 必须在 **kwargs 之前

  • 在函数调用时,*** 可用于解包序列和字典

  • 函数参数顺序:普通参数 → 默认参数 → *args**kwargs

  • 命名建议:坚持使用 *args**kwargs,除非有明确语义需求


思考题

  1. 为什么 *args 打包成元组而不是列表?这体现了 Python 的哪些设计原则?

  2. 如果你在编写一个通用的配置加载函数,如何利用 **kwargs 提高其灵活性?

  3. 在什么情况下,过度使用 *args**kwargs 反而会降低代码可读性和可维护性?如何平衡灵活性与明确性?