python*args和**kwargs怎么用定义函数

追风的人
追风的人
发布时间:2026-11-13| 3人浏览过
原创

在 Python 函数定义中,*args 用于接收任意数量的位置参数并自动打包为元组,**kwargs 用于接收任意数量的关键字参数并自动打包为字典。本文详解其语法机制、标准声明顺序、打包与解包辨析及工程最佳实践。

Python 核心语法指南
在 Python 中定义函数时,*args 用于接收任意数量的未命名位置实参并打包为元组(tuple),**kwargs 用于接收任意数量的具名关键字实参并打包为字典(dict)。星号(* 与 **)是执行参数打包的核心语法操作符,而 args 与 kwargs 是社区通用的形参命名约定。
python*args和**kwargs怎么用定义函数
图 0:*args(位置实参元组打包)与 **kwargs(关键字实参字典收集)概念概览

一、核心结论与快速理解

在编写通用函数、编写装饰器或继承类重写方法时,开发者往往无法预先确定调用者会传入多少个参数。Python 通过单星号 * 和双星号 ** 提供了“可变参数”(Variable-length Arguments)机制:

  • 单星号 *args:收集函数定义中常规位置参数之后出现的所有剩余位置实参,在函数体内以一个 tuple 对象的形式呈现。
  • 双星号 **kwargs:收集所有未能与显式形参匹配的 key=value 键值实参,在函数体内以一个标准的 dict 对象的形式呈现。

值得强调的是,语法层面的强制要求仅在于星号 * 和 **。你完全可以将其命名为 *values 或 **options,代码仍可正常执行。但在实际工程与团队协作中,必须严格遵循 PEP 8 命名规范使用 *args 和 **kwargs,以确保代码的可读性与一致性。

python*args和**kwargs怎么用定义函数
图 1:函数定义中的参数分配与打包机制架构

二、用 *args 定义函数:收集任意数量的位置参数

当函数需要计算不确定数量的数值之和、拼接多段文本或输出日志时,直接在形参列表中声明 *args 是最自然的解决方案。

1. 基本语法与底层容器验证

下面的代码定义了一个计算任意数量数值总和的函数。我们在函数体内部打印 args 的变量值及其类型:

def calculate_sum(base, *args):
    # base 接收第一个必须传入的位置实参
    # args 收集其余所有位置实参
    print(f"基准值 base: {base}")
    print(f"收集到的 args 内容: {args}, 容器类型: {type(args)}")
    
    total = base + sum(args)
    return total

# 示例调用 1:传入多个实参
res1 = calculate_sum(10, 20, 30, 40)
print(f"计算结果 1: {res1}")

# 示例调用 2:仅传入基准值
res2 = calculate_sum(100)
print(f"计算结果 2: {res2}")
控制台示例输出
基准值 base: 10
收集到的 args 内容: (20, 30, 40), 容器类型: <class 'tuple'>
计算结果 1: 100
基准值 base: 100
收集到的 args 内容: (), 容器类型: <class 'tuple'>
计算结果 2: 100

观察输出结果可以得出两个关键事实:

  1. 即使调用者传入了多个整数,解释器也将其原样封装进了一个 tuple。因此,在函数内部你可以安全地对 args 执行 len()、迭代遍历或切片操作。
  2. 当调用者没有传入额外的多余参数时,args 并不会是 None,而是一个空元组 ()。这保证了后续针对 args 的 for x in args: 循环无需额外判断 if args is not None,避免了空指针异常。
注意事项:元组的不可变性

因为 args 是元组而不是列表,所以无法在函数内部调用 args.append() 或对元素进行重新赋值。若必须在函数内增删元素,需显式执行 arg_list = list(args) 进行转换。

python*args和**kwargs怎么用定义函数
图 2:Python 解释器参数解析与绑定四步流程

三、用 **kwargs 定义函数:收集任意数量的关键字参数

在构建数据接口、配置项处理器或数据库查询封装时,参数的名称往往包含业务语义。使用 **kwargs 可以捕获所有调用者显式指定的键值对,无需在函数签名中预先写死每一个可能的参数名。

1. 基本语法与字典特性

下面的示例演示了如何通过 **kwargs 编写一个支持任意配置项的连接构建函数:

def build_connection(host, port=8080, **kwargs):
    print(f"核心地址: {host}:{port}")
    print(f"额外配置 kwargs: {kwargs}, 容器类型: {type(kwargs)}")
    
    # 使用字典标准方法安全读取配置
    timeout = kwargs.get("timeout", 30)
    retry_enabled = kwargs.get("retry", False)
    
    print(f"生效配置: timeout={timeout}, retry={retry_enabled}")

# 示例调用
build_connection("api.example.com", port=443, timeout=10, retry=True, auth_type="Bearer")
控制台示例输出
核心地址: api.example.com:443
额外配置 kwargs: {'timeout': 10, 'retry': True, 'auth_type': 'Bearer'}, 容器类型: <class 'dict'>
生效配置: timeout=10, retry=True

与 *args 类似,若调用端未传递任何多余的具名参数,kwargs 将初始化为一个合法的空字典 {}。在函数体内,建议始终使用 kwargs.get(key, default) 读取参数,这比直接下标取值 kwargs[key] 更具容错性,能优雅地提供缺省值并防止 KeyError。

注意事项:键名必须为合法标识符字符串

在通过 key=value 语法调用函数时,key 在 Python 语法层必须是合法的变量名形式(不能是数字开头或包含破折号)。收集到 kwargs 后,所有的键都会自动转化为标准的字符串类型。

python*args和**kwargs怎么用定义函数
图 3:定义时“打包” (Packing) 与调用时“解包” (Unpacking) 对比

四、辨析易混淆点:定义时“打包”与调用时“解包”

很多初学者常把“函数定义时的可变参数”与“函数调用时的解包操作”混为一谈。虽然它们都使用 * 和 ** 符号,但两者的运作方向截然相反:

对比维度 定义时使用(打包 Packing) 调用时使用(解包 Unpacking)
语法出现位置 函数头声明的形参列表:def fn(*args, **kwargs) 发起调用时的实参列表:fn(*numbers, **options)
执行动作 将零散传入的多个实参聚拢、打包成单个集合 将现有的序列或映射对象拆散、解构为离散实参
操作对象 调用者在控制台输入的各实参片段 预先定义好的 list、tuple、set 或 dict
典型收益 使被调函数能够兼容不同长度的调用请求 免去手动编写索引或硬编码解开列表的重复工作

典型工程实践:透明代理与参数透传

当把“定义时打包”与“调用时解包”结合使用时,就构成了 Python 中极为核心的透明转发模式。这种模式广泛应用于装饰器(Decorator)和面向对象继承(OOP):

import time

# 1. 装饰器中的无损转发
def execution_logger(func):
    # 包装层定义:打包所有入参
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        # 目标函数调用:解包所有入参,原样无损传递
        result = func(*args, **kwargs)
        cost = time.perf_counter() - start
        print(f"[LOG] 函数 {func.__name__} 执行耗时: {cost:.6f}s")
        return result
    return wrapper

@execution_logger
def query_data(endpoint, timeout=5, retries=3):
    return f"Fetched from {endpoint} (retries={retries})"

# 示例调用
msg = query_data("https://api.v1/users", timeout=10, retries=5)
print(f"返回内容: {msg}")
控制台示例输出
[LOG] 函数 query_data 执行耗时: 0.000004s
返回内容: Fetched from https://api.v1/users (retries=5)

由于 wrapper 在定义处声明了 *args, **kwargs,并在内部调用 func(*args, **kwargs),它可以在完全不知道 func 签名细节的情况下,对任意签名的函数实现透明包裹与耗时统计。

python*args和**kwargs怎么用定义函数
图 4:Python 函数形参定义标准顺序与校验规则总结

五、形参组合定义时的严格顺序规则

在复杂函数中,可能会同时出现普通位置参数、带默认值的参数、*args、仅限关键字参数以及 **kwargs。Python 解释器要求这些形参在函数定义中必须严格遵守如下排列次序:

def complex_function(
    pos_only,            # 1. 必选位置参数
    default_param=10,    # 2. 带有默认值的常规参数
    *args,               # 3. 可变位置参数 (*args)
    kw_only_param,       # 4. 仅限关键字参数 (Keyword-only)
    **kwargs             # 5. 可变关键字参数 (**kwargs)
):
    pass

1. 为什么 *args 之后的参数会变成 Keyword-only?

当调用者传入一系列位置实参时,所有超出前置固定形参的未命名值都会被 *args 吞没。因此,声明在 *args 之后的任何形参(如上例中的 kw_only_param),在语法上不可能通过位置匹配得到赋值。调用者必须显式以 kw_only_param="value" 的具名形式传递,否则 Python 会直接抛出异常:

错误类型示例
TypeError: complex_function() missing 1 required keyword-only argument: 'kw_only_param'

2. 常见反例与排错清单

  • 反例 1:将 **kwargs 放在 *args 之前
    代码:def bad_order(**kwargs, *args): pass
    报错:SyntaxError: invalid syntax。因为 **kwargs 已经代表了实参收集的最外围终点,其后不允许再跟任何参数声明。
  • 反例 2:多次传递相同参数
    若函数显式声明了 def send(target, **kwargs): pass,当调用方写出 send("192.168.1.1", target="10.0.0.1") 时,会触发 TypeError: send() got multiple values for argument 'target'。因为 target 已通过第一个位置实参获得绑定,不能再以同名关键字实参重复提供。

六、总结与实践建议

正确使用 *args 和 **kwargs 能显著提升 Python 代码的扩展性与灵活性。在日常编码中,建议掌握以下原则:

  1. 优先明确签名:普通业务函数应优先使用显式的命名形参。只有在参数个数无法提前预知、编写通用装饰器或转发封装上层库时,才引入 *args 和 **kwargs。过多无节制的可变参数会削弱 IDE 的类型提示与自动补全体验。
  2. 区分打包与解包:牢记函数头定义是“打包聚合成容器”,函数体调用是“解包拆分传参”。
  3. 严格恪守声明顺序:按“位置参数 → 默认参数 → *args → 仅限关键字参数 → **kwargs”由左至右书写,避免语法错误。
相关专题:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。