当前位置:首页 > 技术 > 正文内容

🐍 Python args 和 kwargs 简单解释 — 常见错误与解决方案

访客 技术 2026年5月13日 15:18 3

❓ 超越简单示例:args 和 kwargs 的实际应用

python args and kwargs explained simple

Python 中的 *args 和 **kwargs 语法不仅仅是关于传递额外参数;它们是编写能够适应不断发展的接口、干净地包装其他函数以及避免在实际代码库中脆弱参数列表的工具。大多数教程停留在简单的示例,让开发人员不确定如何在生产级代码中应用它们。

📑 目录

  • ❓ 超越简单示例:args 和 kwargs 的实际应用
  • 🐍 *args — 处理可变位置输入
  • 🔧 用例:灵活的日志层
  • ⚠️ 陷阱:顺序很重要
  • 🧩 **kwargs — 处理任意关键字参数
  • 🔧 用例:API 客户端构建器
  • ⚠️ 陷阱:不要盲目转发未知 kwargs
  • 🤝 结合 *args 和 **kwargs 实现完全灵活性
  • 🔍 参数解析如何工作
  • ⚙️ 函数调用中的解包操作
  • 🧠 实际项目中何时使用 args 和 kwargs
  • ✅ 适合使用的情况
  • ❌ 应避免过度使用的情况
  • 📚 示例:灵活的类初始化
  • 🟩 最终思考
  • ❓ 常见问题解答
  • *args 和 **kwargs 可以在函数定义中一起使用吗?
  • 使用 *args 和 **kwargs 有性能成本吗?
  • 如果我传递一个与命名参数匹配的关键字参数,并且也将其包含在 **kwargs 中会发生什么?
  • 📚 参考资料与进一步阅读

🐍 *args — 处理可变位置输入

*args 语法允许函数接受任意数量的位置参数,这些参数会被收集到一个元组中。当 Python 在参数前看到 * 前缀时,它会告诉函数将所有剩余的位置参数打包到一个元组中,可以通过给定的名称访问。这在 CPython 中通过 C 级别的 PyArg_ParseTupleAndKeywords 及相关 API 实现——解释器从调用堆栈动态构建元组。

def 记录操作(用户, 操作, *详情):
    print(f"用户 '{用户}' 执行了 '{操作}'")
    if 详情:
        print(f"详情: {', '.join(str(d) for d in 详情)}")

# 使用
记录操作("alice", "文件上传", "报告.pdf", "大小: 2MB", "加密=True")



用户 'alice' 执行了 '文件上传'
详情: 报告.pdf, 大小: 2MB, 加密=True

🔧 用例:灵活的日志层

包装操作的函数——比如管理系统中的审计日志——通常不知道被包装函数会收到什么参数。*args 允许包装器传递所有位置输入而不做修改。

def 审计日志(func):
    def 包装器(*args, **kwargs):
        print(f"调用 {func.__name__},参数 args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    return 包装器

@审计日志
def 转账(来源ID, 目标ID, 金额, 原因=None):
    print(f"从 {来源ID} 转账 ${金额} 到 {目标ID}")

转账(101, 205, 500, 原因="退款")



调用 transfer_funds,参数 args=(101, 205, 500), kwargs={'原因': '退款'}
从 101 转账 $500 到 205

⚠️ 陷阱:顺序很重要

*args 会消耗所有未匹配的位置参数,因此它必须放在任何必需的位置参数之后。你不能定义像 def 错误函数(*args, x) 这样的函数——Python 会引发 SyntaxError。

🧩 **kwargs — 处理任意关键字参数

**kwargs 语法将任何未匹配的关键字参数收集到一个字典中。从机制上讲,当 Python 处理函数调用时,与形式参数不匹配的关键字参数会被打包到 dict 对象中。这对于配置密集型工作流是高效的,因为字典查找是 O(1),并且这种结构反映了 API 和配置文件中常见的 JSON 样式数据。

def 创建用户(姓名, 邮箱, **个人资料):
    用户 = {"姓名": 姓名, "邮箱": 邮箱}
    用户.update(个人资料)  # 添加可选字段
    print(f"创建用户: {用户}")
    return 用户

# 使用
创建用户("Bob", "bob@example.com", 角色="管理员", 团队="基础设施", 激活=True)



创建用户: {'姓名': 'Bob', '邮箱': 'bob@example.com', '角色': '管理员', '团队': '基础设施', '激活': True}

🔧 用例:API 客户端构建器

在与 REST API 交互时,查询参数或标头通常因端点而异。使用 **kwargs 可以让你编写通用的请求包装器。

import requests

def api_get(端点, **选项):
    基础URL = "https://api.example.com/v1"
    URL = f"{基础URL}/{端点}"

    # 提取特定键,其余作为参数传递
    标头 = 选项.pop('标头', {})
    超时 = 选项.pop('超时', 5)

    响应 = requests.get(URL, params=选项, headers=标头, timeout=超时)
    return 响应.json() if 响应.ok else None

# 灵活的调用
api_get("用户", 角色="开发", 激活=True, 超时=10)
api_get("服务器", 区域="us-west-2", 标头={"Authorization": "Bearer xyz"})

这种模式使你的接口保持简洁,同时允许对 HTTP 参数进行完全控制——所有这些都不会使函数签名膨胀。

⚠️ 陷阱:不要盲目转发未知 Kwargs

将每个未知的关键字参数直接传递给另一个系统可能会引入安全或稳定性风险。在与外部系统交互时,始终验证或清理 **kwargs。

使用 *args 和 **kwargs 来推迟决策,而不是避免设计。

🤝 结合 *args 和 **kwargs 实现完全灵活性

一个函数可以同时接受 *args 和 **kwargs,使其能够包装任何具有任何签名的可调用对象。这种组合是装饰器、中间件和代理函数的基础——特别是在 Django、FastAPI 或 Flask 等框架中,处理程序需要保持对底层签名的无感知。

def 失败重试(最大重试次数=3):
    def 装饰器(func):
        def 包装器(*args, **kwargs):
            for 尝试 in range(1, 最大重试次数 + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"尝试 {尝试} 失败: {e}")
                    if 尝试 == 最大重试次数:
                        raise
            return None
        return 包装器
    return 装饰器

@失败重试(最大重试次数=2)
def 不稳定API调用(用户ID):
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络超时")
    return {"状态": "成功", "数据": f"个人资料_{用户ID}"}

# 尝试调用
不稳定API调用(123)



尝试 1 失败: 网络超时
尝试 2 失败: 网络超时
...
# 可能最终成功或在 2 次尝试后引发异常

🔍 参数解析如何工作

Python 按以下顺序解析函数参数:

  1. 位置参数(与命名参数匹配)
  2. 关键字参数(按名称)
  3. 缺失参数的默认值
  4. *args 收集未匹配的位置参数
  5. **kwargs 收集未匹配的关键字参数

解释器使用堆栈帧来绑定名称,而 * 和 ** 运算符控制如何打包或解包多余的值。

⚙️ 函数调用中的解包操作

就像 *args 在定义时打包位置参数一样,在函数调用中使用 * 会将序列解包为位置参数。

参数 = ["Alice", "编辑帖子", "帖子ID=456", "草稿=True"]
记录操作(*参数)  # 等同于 record_action("Alice", "edit_post", "post_id=456", "draft=True")

同样,** 会将字典解包为关键字参数:

关键字参数 = {
    "姓名": "Charlie",
    "邮箱": "charlie@example.com",
    "角色": "分析师",
    "部门": "数据"
}
创建用户(**关键字参数)

这种双向使用——在定义时打包,在调用时解包——正是 *args 和 **kwargs 语法在动态代码库中如此强大的原因。

🧠 实际项目中何时使用 args 和 kwargs

了解如何使用 *args 和 **kwargs 是不够的——你需要判断何时应用它们。

✅ 适合使用的情况

  • 装饰器——它们必须适用于任何函数签名。
  • API 包装器——当将参数转发到另一个函数或服务时。
  • 基类或混入——通过 super().init(*args, **kwargs) 将参数向上传递 MRO。
  • 配置层——可选设置被向下传递。

❌ 应避免过度使用的情况

  • 函数具有清晰、稳定的接口——显式更好。
  • 你将必需参数隐藏在 **kwargs 后面——这会损害可发现性。
  • 你正在构建公共 API——用户更喜欢自动完成友好的签名。

📚 示例:灵活的类初始化

在继承层次结构中,*args 和 **kwargs 让子类可以在不知道父类完整签名的情况下传递参数。

class 数据库:
    def __init__(self, 主机, 端口, **选项):
        self.主机 = 主机
        self.端口 = 端口
        self.ssl = 选项.get("ssl", False)
        self.超时 = 选项.get("超时", 30)

class MongoDB(数据库):
    def __init__(self, 数据库名, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.数据库名 = 数据库名

# 使用
mongo = MongoDB(
    数据库名="日志",
    主机="10.0.1.100",
    端口=27017,
    ssl=True,
    超时=60
)
print(mongo.__dict__)



{'主机': '10.0.1.100', '端口': 27017, 'ssl': True, '超时': 60, '数据库名': '日志'}

这种模式在 ORM 模型、SDK 和配置系统中很常见——这也是为什么 *args 和 **kwargs 语法在语法之外很重要的实际例子。

🟩 最终思考

*args 和 **kwargs 不仅仅是语法糖——它们是构建 Python 应用程序中可适应、可维护层的工具。明智地使用它们可以减少组件之间的耦合,实现干净的装饰器,并简化继承。

然而,与任何动态功能一样,它们以牺牲一些清晰度为代价换取灵活性。关键在于知道何时使用显式参数锁定接口,何时使用 *args 和 **kwargs 保持开放。在成熟的代码库中,你经常会看到它们在基础设施代码深处使用——中间件、包装器、基类——而公共 API 保持显式和文档化。

掌握 *args 和 **kwargs 语法意味着理解其机制和设计理念:必要时推迟决策,但可以时进行文档化和约束。

❓ 常见问题解答

*args 和 **kwargs 可以在函数定义中一起使用吗?

是的——一个函数可以同时接受 *args 和 **kwargs,只要它们以正确的顺序出现:常规参数,然后是 *args,然后是仅关键字参数或 **kwargs。语法 def func(a, *args, x=1, **kwargs): 是有效的,并且在框架中常用。

使用 *args 和 **kwargs 有性能成本吗?

存在极小的开销:*args 创建一个元组,**kwargs 创建一个字典。这些在 CPython 中是轻量级操作。更大的问题是可读性和调试——当参数隐藏在 *args 和 **kwargs 后面时,堆栈跟踪和 IDE 提示可能不够精确。

如果我传递一个与命名参数匹配的关键字参数,并且也将其包含在 **kwargs 中会发生什么?

Python 会为模糊赋值引发 TypeError。例如,如果一个函数有 name 参数,你不能同时将其作为位置/关键字参数和 **kwargs 的一部分传递。解释器严格解析名称并防止重复。

📚 参考资料与进一步阅读

  • 官方 Python 文档中关于调用和定义的内容——深入涵盖 *args 和 **kwargs:docs.python.org
  • Python 数据模型参考中关于函数调用解析的内容:docs.python.org
  • 使用 *args 和 **kwargs 的实际装饰器模式:docs.python.org

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。