- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
在 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: 20shost、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,b | POSITIONAL_ONLY | 仅限位置 |
c | POSITIONAL_OR_KEYWORD | 位置或关键字皆可 |
d | KEYWORD_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"两类场景下的参数边界控制,而本文的/+*组合则是其中约束最严格、语义最完整的一种。
实战建议:何时该使用严格参数边界
综合上面的代码与元数据验证,可以总结出几条实用判断标准:
- 位置顺序本身承载语义(如
connect(host, port, /, ...)):/可以防止调用方写出connect(host=..., port=...)这种让代码与直觉顺序脱节的写法,也让未来重命名形参时不会悄然破坏外部调用。 - 布尔标志与可选配置(如
timeout=30、verbose这类带默认值的参数):放在*右侧强制命名,调用处timeout=20的语义远优于一个裸数字20,这也正是 force-remaining-arguments-to-be-named.md 中作者推荐的做法。 - 需要前后兼容的公开 API:
/允许你在未来自由调整内部实现而无需担心调用方依赖参数名,是标准库和框架维护者常用的兼容性手段。
需要注意的是,严格边界是一把双刃剑:它牺牲了传参灵活性来换取调用方代码的可读性与稳定性。因此它更适合面向外部使用者的公共函数、CLI 入口与配置型构造器,而不必应用到每一个内部辅助函数上。
小结
/与*的组合为 Python 函数提供了最严格的参数边界控制:/左侧只能按位置传、*右侧只能按关键字传,两者之间则保留默认的混合语义。运行时TypeError会在误用时精确指出违规参数名,Pyright 等静态检查器则在编辑器阶段就给出call-arg诊断,而inspect.signature的POSITIONAL_ONLY/KEYWORD_ONLY枚举则从元数据层面印证了这一机制。结合仓库中关于*与 dataclass 关键字限定字段的三篇姊妹笔记,你可以在普通函数、类构造器与 dataclass 中自如地设计出清晰、稳定、易于维护的参数契约。
- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
相关推荐
Python 3函数参数类型完全解析:位置、关键字与可变参数
Python 3函数参数类型完全解析:位置、关键字与可变参数 想要真正掌握Python编程?那么函数参数类型绝对是你必须深入理解的核心概念!🚀 在Python
文档教程AkVirtualCamera终极指南:5分钟掌握跨平台虚拟摄像头配置
AkVirtualCamera终极指南:5分钟掌握跨平台虚拟摄像头配置 想要在视频会议中展示精美演示文稿?需要在直播时使用自定义视频源?AkVirtualCam
终极Jinja函数调用指南:掌握位置参数与关键字参数的实用技巧
终极Jinja函数调用指南:掌握位置参数与关键字参数的实用技巧 Jinja是一款强大的模板引擎,广泛应用于Python Web开发中。在Jinja模板中,函数调
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考