
Python 函数参数分隔符 *Keyword-Only Arguments 原理与实践一、原理与实践1、引言2、从 *args 到分隔符* 的双重身份2.1 、打包参数*args2.2 、分隔参数裸 *3、 Keyword-Only Arguments 的语法规则3.1、 基本形式3.2 、与默认值结合3.3 、与 **kwargs 结合4、 为什么需要 Keyword-Only Arguments4.1、 提升代码可读性4.2 、避免参数顺序错误4.3、 为未来扩展预留空间4.4、 配合 *args 使用5、 深入原理Python 如何解析参数5.1、 参数解析的两阶段模型5.2、 字节码视角5.3 、与 inspect 模块的交互6、 实践案例6.1、 配置类函数6.2 、API 封装6.3、 数据校验函数7、常见误区与注意事项7.1 、* 之后不能再有位置参数7.2、 不要与解包混淆7.3 、与位置限定符 / 的对比8、 总结二、代码示例1、示例代码2、运行输出一、原理与实践1、引言在 Python 的函数定义中*是一个容易被忽视却又极其重要的符号。很多初学者第一次见到它是在*args可变参数中但单独出现在参数列表中间的*则扮演着完全不同的角色——它将后面的所有参数强制变为「仅限关键字参数」Keyword-Only Arguments。本文将深入剖析这个分隔符的工作原理、使用场景与最佳实践帮助你彻底掌握这一语言特性。2、从*args到分隔符*的双重身份要理解 Keyword-Only Arguments首先需要厘清*在函数定义中的两种用法。2.1 、打包参数*args当*后面紧跟一个变量名时它负责收集所有多余的位置参数打包成元组deffunc(*args):print(args)func(1,2,3)# 输出: (1, 2, 3)2.2 、分隔参数裸*当*单独出现、后面不跟变量名时它不收集任何参数只作为一个「分界线」deffunc(a,*,b):print(a,b)func(1,b2)# 正确func(1,2)# TypeError: func() takes 1 positional argument but 2 were given此时*之后的所有参数如b只能通过关键字传递无法再按位置传入。3、 Keyword-Only Arguments 的语法规则3.1、 基本形式deffunc(a,*,b,c):passa普通位置参数可位置传参也可关键字传参。b、cKeyword-Only 参数只能以keywordvalue形式传入。3.2 、与默认值结合Keyword-Only 参数同样支持默认值且不要求放在无默认值参数之后这与普通位置参数不同deffunc(a,*,b10,c20):print(a,b,c)func(1)# 输出: 1 10 20func(1,b2)# 输出: 1 2 20func(1,c3)# 输出: 1 10 33.3 、与**kwargs结合*分隔符可以与**kwargs同时使用此时*之后、**kwargs之前的参数是显式声明的 Keyword-Only 参数deffunc(a,*,b,**kwargs):print(a,b,kwargs)func(1,b2,c3,d4)# 输出: 1 2 {c: 3, d: 4}4、 为什么需要 Keyword-Only Arguments4.1、 提升代码可读性当函数参数较多且含义容易混淆时强制关键字传参能让调用意图一目了然# 不推荐位置参数过多调用时难以分辨defcreate_user(name,age,city,phone):passcreate_user(张三,25,北京,13800000000)# 推荐关键信息用 Keyword-Only 强制声明defcreate_user(name,*,age,city,phone):passcreate_user(张三,age25,city北京,phone13800000000)4.2 、避免参数顺序错误位置参数一旦顺序写错程序不会报错但会产生难以察觉的逻辑 bug。强制关键字传参可以从语法层面杜绝这类问题。4.3、 为未来扩展预留空间在函数签名中插入*可以在不破坏现有调用方式的前提下后续安全地新增 Keyword-Only 参数。4.4、 配合*args使用当函数同时需要可变位置参数和具名可选参数时*分隔符几乎是必需品deflog(level,*messages,timestampNone):print(f[{level}],*messages,timestampor)log(INFO,hello,world,timestamp2026-01-01)5、 深入原理Python 如何解析参数5.1、 参数解析的两阶段模型CPython 在调用函数时对参数的处理分为两个阶段位置参数匹配按顺序将实参绑定到形参遇到*分隔符时停止。关键字参数匹配将剩余的keyvalue实参按名字绑定到形参。*分隔符的本质是在第一阶段与第二阶段之间划出一道不可逾越的边界。5.2、 字节码视角通过dis模块可以观察到带*分隔符的函数在字节码层面并无特殊指令其约束是在参数解析阶段由解释器强制执行的importdisdeffunc(a,*,b):passdis.dis(func)输出中可以看到LOAD_FAST等常规指令说明*的约束发生在函数调用协议层而非函数体内部。5.3 、与inspect模块的交互使用inspect.signature可以清晰地看到参数的分类importinspectdeffunc(a,*,b,c10):passsiginspect.signature(func)forname,paraminsig.parameters.items():print(name,param.kind)输出a POSITIONAL_OR_KEYWORD b KEYWORD_ONLY c KEYWORD_ONLYparam.kind为KEYWORD_ONLY的参数正是*分隔符之后的参数。6、 实践案例6.1、 配置类函数defconnect(host,*,port3306,timeout5,use_sslFalse):连接数据库连接参数必须显式声明。print(f连接{host}:{port}超时{timeout}sSSL{use_ssl})connect(localhost)connect(localhost,port5432,use_sslTrue)6.2 、API 封装defrequest(url,*,methodGET,headersNone,timeout3):HTTP 请求封装method/headers/timeout 均为 Keyword-Only。headersheadersor{}print(f{method}{url}headers{headers}timeout{timeout})request(https://api.example.com)request(https://api.example.com,methodPOST,headers{Content-Type:application/json})6.3、 数据校验函数defvalidate(data,*,required_fieldsNone,min_length0):校验数据必填字段与最小长度均需关键字指定。required_fieldsrequired_fieldsor[]missing[fforfinrequired_fieldsiffnotindata]ifmissing:raiseValueError(f缺少字段:{missing})iflen(data)min_length:raiseValueError(数据长度不足)returnTruevalidate({name:张三},required_fields[name,age])7、常见误区与注意事项7.1 、*之后不能再有位置参数deffunc(*,a,b):# 正确passdeffunc(*,a,b1):# 正确pass7.2、 不要与解包混淆函数定义中的*是分隔符而函数调用中的*iterable是序列解包二者作用完全不同deffunc(a,*,b):passargs[1]kwargs{b:2}func(*args,**kwargs)# 调用时 * 用于解包合法7.3 、与位置限定符/的对比Python 3.8 引入了/用于限定仅限位置参数Positional-Only。两者可同时使用顺序为deffunc(a,/,b,*,c):passa仅限位置参数。b位置或关键字均可。c仅限关键字参数。8、 总结特性说明语法def func(a, *, b)作用强制*之后的参数只能以关键字方式传入适用场景参数较多、含义易混淆、需要为扩展预留空间与*args区别*args收集位置参数裸*只做分隔与/区别/限定仅位置参数*限定仅关键字参数Keyword-Only Arguments 是 Python 函数签名设计中一项优雅而实用的特性。它通过语法层面的约束帮助开发者写出更清晰、更健壮的代码。建议在编写公共 API、配置类函数或参数较多的函数时主动考虑使用*分隔符让函数接口的意图更加明确。二、代码示例1、示例代码fromtypingimportTypeVar TTypeVar(T)deffull_function(pos1:int,pos2:str,*args,kw_mandatory:float,kw_default:int100,**kwargs): 参数规则拆解 pos1, pos2 : 普通位置参数支持位置 / 关键字传参 *args : 收集多余的位置参数可选 kw_mandatory : *后面无默认 → 必须关键字传参必填 kw_default : *后面带默认 → 关键字传参可选 **kwargs : 捕获额外自定义关键字参数放最后 print(函数输出)print(fpos1 {pos1})print(fpos2 {pos2})print(fargs {args})print(fkw_mandatory {kw_mandatory})print(fkw_default {kw_default})print(fkwargs {kwargs}\n)# ---------------------- ✅合法调用示例 ----------------------# 1.基础用法full_function(10,modbus,kw_mandatory3.14)# 2.携带多余位置参数给 *argsfull_function(20,rtu,99,88,kw_mandatory5.20,kw_default666)# 3.追加额外自定义参数给 **kwargsfull_function(30,tcp,kw_mandatory1.23,timeout0.5,slave1)# 4.前面全部改成关键字传参Python允许full_function(pos140,pos2uart,kw_mandatory9.99)# ---------------------- ❌非法调用放开注释运行会报错 ----------------------# 1. kw_mandatory 使用位置传参# full_function(10, test, 3.14)# 2. 缺失必填关键字参数 kw_mandatory# full_function(10, test)# 3. **kwargs 不能使用位置传参# full_function(10,test,kw_mandatory1, 0.5)# 类方法工程实例复刻 pymodbus API风格 classModbusClient:defread_holding_register(self,address:int,*,count:int,# 必填关键字参数无默认device_id:int1,# 可选关键字参数带默认值timeout:float0.8,no_response_expected:boolFalse)-T:Modbus 03功能码读取保持寄存器print(f【Modbus 03】)print(f起始地址{address})print(f读取寄存器数量{count})print(f从站ID{device_id})print(f超时时间{timeout})print(f无需应答{no_response_expected}\n)returnNoneif__name____main__:cliModbusClient()# ✅正确调用cli.read_holding_register(0,count10)cli.read_holding_register(100,count5,device_id2,timeout1.5)cli.read_holding_register(200,count1,no_response_expectedTrue)# ❌错误调用count禁止位置传参# cli.read_holding_register(0, 10)2、运行输出(.venv)PS D:\user\01417804\桌面\PythonProjectpython.\main.py函数输出pos110pos2modbus args()kw_mandatory3.14kw_default100kwargs{}函数输出pos120pos2rtu args(99,88)kw_mandatory5.2kw_default666kwargs{}函数输出pos130pos2tcp args()kw_mandatory1.23kw_default100kwargs{timeout:0.5,slave:1}函数输出pos140pos2uart args()kw_mandatory9.99kw_default100kwargs{}【Modbus03】 起始地址0读取寄存器数量10从站ID1超时时间0.8无需应答False【Modbus03】 起始地址100读取寄存器数量5从站ID2超时时间1.5无需应答False【Modbus03】 起始地址200读取寄存器数量1从站ID1超时时间0.8无需应答True(.venv)PS D:\user\01417804\桌面\PythonProject