☰
Python 参数边界控制:用 `/` 与 `*` 严格隔离位置参数与关键字参数
2026/10/8 1:55:04 网站建设 项目流程
  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

在 Python 中定义函数时,默认情况下每个参数既可以按位置传入,也可以按关键字(name=value)传入,甚至可以混合使用。但在设计公共 API、CLI 配置函数或需要保证调用方语义清晰时,我们往往希望强制一部分参数只能按位置传入、另一部分只能按关键字传入。本文基于 til 仓库中的 strictly-separate-positional-and-keyword-arguments.md,系统讲解如何用位置限定符/(positional-only marker)与关键字限定符*(keyword-only marker)在函数定义中建立严格的参数边界,并给出可复现的代码示例、错误信息分析、类型检查器提示以及inspect层面的验证方法。

默认的灵活传参方式:位置与关键字随意混用

通常情况下,Python 函数参数既可以按位置传递,也可以按关键字传递,二者还能混用——唯一硬性约束是:所有位置参数必须排在所有关键字参数之前。例如下面这个函数:

def describe(host, port, verbose): print(host, port, verbose)

以下调用方式全部合法:

describe("localhost", 3000, True) # 全按位置 describe(host="localhost", port=3000, verbose=True) # 全按关键字 describe("localhost", 3000, verbose=True) # 位置 + 关键字混用

这种灵活性在日常脚本中很便利,但它也带来一个现实问题:调用方可以随意把host、port写成关键字形式,函数内部一旦重命名参数,外部调用就可能悄然失配。当参数语义对顺序高度敏感(例如网络连接的目标地址与端口)时,我们更希望强制调用方按位置传入,避免误用。

两个标记符:/与*的职责

Python 的函数签名中内置了两个"边界标记":

标记名称位置作用
/positional-only marker参数列表中间或末尾位于其左侧的所有参数只能按位置传递
*keyword-only marker参数列表中间或末尾位于其右侧的所有参数只能按关键字传递

两个标记可以单独使用,也可以像本篇主题那样组合使用,从而把参数列表切成三段:位置限定区 | 普通区(位置/关键字皆可) | 关键字限定区。

组合使用:让connect的参数边界清晰可读

原文档给出了一个非常典型的网络连接示例。connect函数的签名如下:

def connect(host, port, /, *, timeout=30): print(f"Connecting to #{host}:#{port}") print(f" Timeout: {timeout}s") # ...

逐段解读这条签名:

  • host与port位于/左侧,因此必须按位置传入——这样连接目标在调用处始终以"先主机、后端口"的直观顺序出现;
  • timeout位于*右侧,因此必须按关键字传入——它带有默认值30,调用方不传时自动使用 30 秒,传入时必须显式写成timeout=20这样的形式。

正确调用:位置 + 关键字

>>> connect("localhost", 3000, timeout=20) Connecting to #localhost:#3000 Timeout: 20s

host、port以位置传入,timeout以关键字传入,组合使用两种标记的效果立即可见。

错误调用:把位置限定参数当关键字用

>>> connect(host="localhost", port=4000) Traceback (most recent call last): File "/Users/lastword/dev/misc/python-experiments/arguments.py", line 37, in <module> connect(host="localhost", port=4000) TypeError: connect() got some positional-only arguments passed as keyword arguments: 'host, port'

第二次调用试图把host和port作为关键字参数传入,立刻在运行时抛出TypeError,错误信息明确告诉我们:这两个参数是 "positional-only arguments"(仅限位置参数),却被以关键字形式传递了。错误信息中会精确列出违规的参数名(这里是'host, port'),便于快速定位调用方的问题。该报错信息已在 Python 3.12.10 环境下实测复现,与文档记录完全一致。

编辑器中的静态类型检查:问题在运行前就被发现

除了运行时TypeError,这类误用还会在编辑器中以静态类型错误的形式提前暴露。原文档作者在编辑器中看到该行同时出现两个类型检查错误:

call-arg: Unexpected keyword argument "port" for "connect"

以及针对host的同等报错。也就是说,只要配合 Pyright 这类类型检查器(仓库中相关的配置笔记包括 enable-pyright-type-checking-in-cursor.md、set-up-pyright-type-checking-in-github.md 以及 basedpyright-will-use-pyright-config.md),在保存代码的瞬间就能看到call-arg诊断,而不是等到 CI 或运行时才暴露。

用inspect验证参数分类:三种参数区间

Python 标准库的inspect.signature可以从元数据层面验证每个参数的真实种类(Parameter.kind),这也是理解/与*底层语义最直接的手段。对上面的connect函数:

import inspect def connect(host, port, /, *, timeout=30): pass sig = inspect.signature(connect) print(sig) # (host, port, /, *, timeout=30) for name, param in sig.parameters.items(): print(name, "->", param.kind.name)

输出结果为:

(host, port, /, *, timeout=30) host -> POSITIONAL_ONLY port -> POSITIONAL_ONLY timeout -> KEYWORD_ONLY

两个标记把参数空间切成了清晰的三个区间,我们可以用一个同时包含三种参数的函数一次性观察全貌:

def f(a, b, /, c, *, d): pass

对应的参数分类为:

参数kind枚举值允许的传参方式
a,bPOSITIONAL_ONLY仅限位置
cPOSITIONAL_OR_KEYWORD位置或关键字皆可
dKEYWORD_ONLY仅限关键字

/左侧是位置限定区,*右侧是关键字限定区,二者之间的c仍保留默认的"两种方式皆可"语义。理解了这张表,组合使用/与*时就不会再对边界感到困惑。

术语澄清:关键字参数与命名参数

原文档在结尾补充了一个值得注意的术语细节:在《Python in a Nutshell, 4th Edition》中,更严谨的叫法是Named Arguments(命名参数)而非 Keyword Arguments(关键字参数)。二者指代的是同一事物——以name=value形式传递的参数——但在阅读官方文档、第三方库源码或书籍时,遇到 "keyword-only"、"keyword-only marker" 与 "named arguments" 混用的情况,需要意识到它们是同一概念的不同表述。在 Python 官方文档与类型检查器的报错文案(如Unexpected keyword argument)中,"keyword" 一词仍是最主流的用法。

与仓库中其他 TIL 的呼应:*与 dataclass 中的关键字限定

*与/并非只能成对出现,单独使用其中一个标记就能解决一类常见问题,til 仓库中恰好有几篇与本文构成完整知识闭环的笔记:

  • 只强制关键字、不强制位置:如果只想要求部分参数必须以命名方式传入,单独使用*即可,参见 force-remaining-arguments-to-be-named.md。该文展示了在CliContext.__init__中紧随self放置*,从而强制verbose、repo必须显式命名;同时也演示了带收集器形式def build_identifier(first, *rest, delimiter="/")的用法——此时若不给delimiter命名,它会被*rest一并收走,导致分隔符失效。
  • dataclass 字段的关键字限定:在dataclass中,可以用field(kw_only=True)把某个字段标记为仅限关键字,参见 configure-other-attributes-of-dataclass-field.md;也可以用KW_ONLY哨兵值把其后的所有字段整体划入关键字限定区,参见 another-way-to-mark-keyword-only-dataclass-fields.md。

这四篇笔记合起来覆盖了"普通函数 + dataclass"两类场景下的参数边界控制,而本文的/+*组合则是其中约束最严格、语义最完整的一种。

实战建议:何时该使用严格参数边界

综合上面的代码与元数据验证,可以总结出几条实用判断标准:

  1. 位置顺序本身承载语义(如connect(host, port, /, ...)):/可以防止调用方写出connect(host=..., port=...)这种让代码与直觉顺序脱节的写法,也让未来重命名形参时不会悄然破坏外部调用。
  2. 布尔标志与可选配置(如timeout=30、verbose这类带默认值的参数):放在*右侧强制命名,调用处timeout=20的语义远优于一个裸数字20,这也正是 force-remaining-arguments-to-be-named.md 中作者推荐的做法。
  3. 需要前后兼容的公开 API:/允许你在未来自由调整内部实现而无需担心调用方依赖参数名,是标准库和框架维护者常用的兼容性手段。

需要注意的是,严格边界是一把双刃剑:它牺牲了传参灵活性来换取调用方代码的可读性与稳定性。因此它更适合面向外部使用者的公共函数、CLI 入口与配置型构造器,而不必应用到每一个内部辅助函数上。

小结

/与*的组合为 Python 函数提供了最严格的参数边界控制:/左侧只能按位置传、*右侧只能按关键字传,两者之间则保留默认的混合语义。运行时TypeError会在误用时精确指出违规参数名,Pyright 等静态检查器则在编辑器阶段就给出call-arg诊断,而inspect.signature的POSITIONAL_ONLY/KEYWORD_ONLY枚举则从元数据层面印证了这一机制。结合仓库中关于*与 dataclass 关键字限定字段的三篇姊妹笔记,你可以在普通函数、类构造器与 dataclass 中自如地设计出清晰、稳定、易于维护的参数契约。

  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

相关推荐

上一篇:5分钟快速入门DeepXDE:科学机器学习与物理信息学习的终极指南
下一篇:B站视频数据批量采集与分析工具高效使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询