场景
外卖点单时,本来想在“备注”里写“不要辣”,结果不小心输错位置,填在了“地址”栏。骑手到了,给你打电话:“请问您是在‘不要辣小区’门口等我吗?”
是不是听起来有点好笑?
生活里,错误填写表单位置尚且令人哭笑不得;而在代码世界里,如果参数用错了位置,后果往往不仅仅是尴尬,还可能是致命 bug。
Python 作为一个优雅又宽容的语言,给开发者提供了极大的灵活性,尤其在函数参数传递上,你可以用位置参数、关键字参数,甚至二者混搭。
可是,你知道吗?这种“随意”,有时候会埋下隐患。
所以,Python 给我们提供了一种能力—— 可以“强迫”别人乖乖按规定方式传参:
• 有的参数只能按位置传,
• 有的参数只能用关键字传,
• 有的参数你可以混搭,但你得提前说清楚。
今天,我们就来讲讲“仅限位置参数”和“仅限关键字参数”这两个看似高级的小知识点。
放心,读完之后,你再看到/和*这两个符号,不仅不会头疼,还能体会到它们背后的设计智慧!
Python 函数参数的江湖故事
在讲“限制”之前,我们先聊聊 Python 参数传递的基本套路。
Python 函数定义的时候,参数一般有三种常见形式:
- 位置参数(positional arguments)
你必须按照函数定义时的顺序一个个传进去。
比如:
def greet(name, message):print(f"{name}, {message}")
greet("小明", "早上好")
"小明"会传给name,"早上好"会传给message,非常直观。
- 关键字参数(keyword arguments)
你可以显式地用参数名=值的形式指定,顺序无所谓,反正 Python 会根据参数名自动对号入座。
greet(message="晚安", name="小红")
- 混搭模式
也就是有些位置参数直接传值,有些用关键字指定。
greet("小张", message="下午好")
但是注意:位置参数必须写在前面,关键字参数在后面。否则 Python 会报错。
灵活 VS 潜藏危机
这种高度自由听起来很好,但灵活过了头,有时会让人“用错位置”而不自知。
举个例子:
假设有一个函数 order(item, quantity, price):
order("苹果", 10, 5.0)
一切正常。
如果你用关键字参数:
order(price=5.0, quantity=10, item="苹果")
顺序无所谓,照样跑通。
但问题来了:
order("苹果", 5.0, 10)
这也可以运行,只不过 quantity 和 price 完全颠倒了。
代码不会报错,但你以为买了 10 个苹果,每个 5 元;结果变成买了 5 元钱的苹果,每个 10 个单位??
如果是商业结算系统,这种“人类粗心”带来的 bug,可能导致巨额损失。
这就是为什么,Python 提供了“仅限关键字参数”和“仅限位置参数”的高级用法。它们就像“强制提示”,告诉用户:
👉 这个参数,你必须按位置给我!
👉 这个参数,你必须带上参数名!
接下来,我们分别拆开来讲。
仅限关键字参数的魔法
咱们先来看一个问题:
假设你写了一个函数 combine(a, b),用来把两个列表合并:
def combine(a, b):return list(a) + list(b)
用起来很简单:
combine([1, 2, 3], [4, 5, 6])
# 输出: [1, 2, 3, 4, 5, 6]
一切正常。
后来你觉得,要不要加个“可选设置”,比如:
• 如果用户传了一个 validator 函数,就先对所有元素做验证,通过的才合并。
于是你改成这样:
def combine(a, b, validator=None):result = list(a) + list(b)if validator:result = [x for x in result if validator(x)]return result
看起来功能增强了,很棒,对吧?
但问题也来了:
如果有个用户(或者是未来的你自己)粗心大意,调用时写成了:
combine([1, 2], "not a list")
Python 会把第二个参数“not a list”传给 b,而 validator 没传,默认为 None,这时候程序会崩溃,或者结果不符合预期。
更恐怖的是,如果别人把参数位置搞错了:
combine([1, 2], lambda x: x > 0)
这个 lambda 本意是 validator,但却传给了 b。b 成了一个函数,程序一运行,直接大崩盘。
这种错位,最危险的是:
它不一定立刻报错,而是会悄悄地给你一个“错误但合法”的结果,等你上线了才出事!
所以怎么办?
Python 给出的解决方案就是:把 validator 设置成“只能用关键字传参”。
也就是说,你得明确告诉 Python:“validator 这个参数,你不能偷懒直接用位置传,你必须写成 validator=XXX
。”
语法小抄:一个星号 *
在函数参数列表里,加一个星号 *
,星号之后的参数,就都是“只能通过关键字”来传递的。
def combine(a, b, *, validator=None):result = list(a) + list(b)if validator:result = [x for x in result if validator(x)]return result
现在,你再去调皮地这样写:
combine([1, 2], lambda x: x > 0)
Python 会直接翻脸报错:
TypeError: combine() takes 2 positional arguments but 3 were given
它强迫你必须这样写:
combine([1, 2], [3, 4], validator=lambda x: x > 0)
瞬间世界清净了。
为什么要强制用关键字参数?
- 避免“把参数放错位置”这种低级错误
举个现实生活例子:
假设你开了一家进货公司,后台有一个函数:
def purchase(quantity, price):pass
有一天,一个员工写了:
purchase(10000, 4500)
意思是采购 1 万个产品,每个价格 4,500 元。
另一个员工手快打成了:
purchase(4500, 10000)
这意味着要采购 4,500 个产品,每个价格 1 万元!!
还好你后台有权限审批,不然公司资金链都断了。
如果你当初强制要求他们必须写成:
purchase(quantity=10000, price=4500)
那么这样的错误几乎不可能发生。
关键字参数就是在帮你防人类粗心的。
- 参数作为“可选设置”,需要显式说明
很多时候,函数有一些“默认参数”,比如 validator、timeout、retry_count 这种,它们并不是必需的核心参数,而是一些“调节旋钮”。
如果放任这些参数随意通过位置传参,会导致调用者读代码时一头雾水:
do_something(42, "hello", True, 0.5, None, 7)
看到这里,你知道 True 和 0.5 分别是啥吗?根本看不懂。
但如果强制要求关键字参数:
do_something(42, "hello", retry=True, timeout=0.5, logger=None, max_attempts=7)
是不是一眼明了?
3. 为未来升级留出余地
如果你今天把某个参数设计成“只能用关键字传”,那么将来即使函数内部调整参数名、顺序、实现方式,外部调用基本不会受到影响。
换句话说,这是在为代码的长期可维护性埋下伏笔。
代码不是今天写完就完事的,三个月后自己再回头看,只有“清晰”两个字能救你。
* 和 *args 的区别
• *args 是用来接收任意数量的位置参数,把它们装进一个元组。
• 而单独的 *,不带名字,只是一个“标志符号”,告诉 Python:“从这个星号开始,后面的参数必须用关键字传参。”
用 *args 的话,容易出现“有人乱传额外参数”的风险;
而用单独 *,Python 会严格帮你把关,省心。
小结:
关键字参数
的设计初衷很简单:它就是一个“强制提示框”,防止大家糊里糊涂搞错顺序,让接口使用更安全。
如果你遇到函数有很多“调节项”、配置参数、容易混淆的值,一定要用 * 把这些参数变成“仅限关键字参数”。
这是一个聪明程序员留给未来自己的善意提醒。
仅限位置参数的妙用
前面我们聊了“仅限关键字参数”,
现在来聊聊它的“兄弟”——仅限位置参数。
你可能会疑惑:既然关键字参数这么安全、清晰,为什么还需要“仅限位置参数”?
其实,位置参数有它天然的优势:
• 速度快(解释器解析时更高效)
• 简洁(写代码时不用每次都写参数名)
• 对一些“非常明确、常用、不能模糊”的参数, 位置传参反而更符合直觉。
假设你去奶茶店点单:“我要一杯 大杯 珍珠奶茶,去冰,三分糖。”
服务员只需要按顺序记录:
[品类, 杯型, 冰度, 糖度]
如果有人非要写成:
order(drink='珍珠奶茶', cup_size='大杯', ice='去冰', sugar='三分糖')
虽然没毛病,但显得啰嗦。
这种明确固定的顺序,本来就约定俗成,大家都懂。
如果每次点奶茶还得喊出参数名,是不是有点像做测试题?😂
Python 的语法:一个斜杠 /
在 Python 中,在函数参数列表里加一个斜杠 /,表示这个斜杠前面的参数,都必须按位置传递,不能用关键字。
比如:
def add(a, b, /):return a + b
这时候只能这样写:
add(2, 3) # OK
你要是写成:
add(a=2, b=3) # 报错
Python 会直接告诉你:
TypeError: add() got some positional-only arguments passed as keyword arguments: 'a, b'
为什么 Python 要有 /?
- 保护函数内部参数名
有时候,函数参数名只是内部实现细节,可能以后会改。
如果用户可以用关键字传参,参数名一旦改了,用户代码就会崩溃。
比如 Python 内置函数 math.pow(x, y),
你有没有发现它不能写成:
math.pow(x=2, y=3)
为什么?
因为 math.pow 是底层 C 实现,它参数名可能根本不是 x, y,Python 不希望你依赖这些名字。
换句话说:“参数名是内部机密,你只能按顺序用。”
- 让调用更简洁、直观
很多函数用途非常明确,参数很少,
按位置传参更干脆。
比如 min(a, b)、max(a, b)、pow(a, b),
如果一定要强制写成 min(a=1, b=2),
反而显得累赘。
有些函数天生就是:
• 参数少
• 顺序固定
• 一看就懂
不如让它简简单单用位置参数。
- 性能优化
位置参数解析,比关键字参数快。
底层 C API 和 Python 内核很多函数,
为了效率,都规定只能位置传参。
像 list.append() 就只能写成:
mylist.append(42)
不能 append(item=42),否则直接报错。
在高性能场景,这不是“吹毛求疵”,而是毫秒级的优化积累。
/ 的实际用法
你可以在自己定义的函数中用 /,强制前面参数只能按位置传:
def make_order(drink, size, /, sugar="正常", ice="正常"):print(f"饮品:{drink}, 大小:{size}, 糖度:{sugar}, 冰度:{ice}")
现在调用:
make_order("珍珠奶茶", "大杯", sugar="半糖", ice="少冰")
没有问题。
但如果你想写成:
make_order(drink="奶盖茶", size="中杯")
就会提示错误:
TypeError: make_order() got some positional-only arguments passed as keyword arguments: 'drink, size'
强制性就体现在这儿!
小细节提醒
• / 只能放在参数列表里,且出现在位置参数和其他参数的分界点。
• 一个函数里可以同时有 / 和 ,/ 代表“仅限位置参数”, 代表“仅限关键字参数”。
比如:
def func(a, b, /, c, *, d):pass
这个函数意思是:
• a, b 只能位置传参
• c 可以位置或关键字
• d 只能关键字传参
小结
“仅限位置参数”的用法虽然不常在自己代码里用,但你一定会在 Python 内置函数或者第三方库源码里看到。
它的存在,是为了:
• 隐藏实现细节
• 保持接口简洁
• 防止外部依赖内部变量名
• 提升运行效率
如果以后你写的是一个“底层接口”或者“框架级别”的函数,需要暴露给其他人调用,并且不希望别人乱用参数名,那么 / 就是你的利器!
/ 与 * 的组合魔法——打造完美函数接口
在前面,我们分别讲了“仅限位置参数”和“仅限关键字参数”。
聪明的你可能已经开始好奇了:“能不能把 / 和 * 结合起来,让函数参数设计既严格又灵活,还能防止出错?”
答案是:当然可以!
Python 不仅支持,而且还非常推荐你这么做。
最标准的“组合姿势”:
def func(a, b, /, c, d=1, *, e, f=2):pass
这条语句读起来有点绕,
咱们一句一句拆开:
• a, b, /
:这俩参数只能位置传参,防止别人瞎用参数名。
• c, d=1
: 这部分参数可以用位置,也可以用关键字;d 还有默认值。
• *, e, f=2
:星号之后,所有参数必须用关键字形式传;
• e 必填,f 有默认值。
调用时长这样:
func(10, 20, 30, e=50)
# 正确!a=10(位置)、b=20(位置)、c=30(位置或关键字都可)、e=50(关键字)
要是写成:
func(a=10, b=20, c=30, e=50)
直接报错,因为 a、b 被限制只能位置传参。
这有什么用?
-
防止误用:有些参数不希望被用户乱命名,比如内部接口参数或者简短参数名(x, y, z)。
-
明确责任:必须关键字传入的参数,往往是“开关型”“配置型”的。强制你用 key=value,减少“看不懂函数参数”的尴尬。
-
让接口既安全又优雅:有些参数用位置传,快速方便;有些配置项用关键字传,直观易懂。
这,就是高级 Python 开发者常用的“函数接口设计哲学”:
能明确用位置参数解决的问题,不让别人乱用关键字;
能避免误会的地方,强制关键字。
经典例子:内置函数的设计
很多 Python 内置函数,都把 /
和 *
用得炉火纯青。
比如:
range(stop)
range(start, stop[, step])
你有没有发现 range 的参数都是位置参数,不能写成:
range(start=0, stop=10, step=2) # 报错!
因为 start, stop, step 本质上只是顺序参数。
位置参数 + 固定顺序,比让你背一堆参数名要简单得多。
实战案例:
场景
你正在设计一个支付接口函数:
def pay(order_id, amount, /, *, currency="CNY", payment_method="alipay"):print(f"支付订单 {order_id},金额 {amount} {currency},方式 {payment_method}")
调用时:
pay("ORD123456", 99.9, payment_method="wechat")
• order_id 和 amount 只能位置传参,防止误会。
• currency 和 payment_method 必须关键字传参,清晰易懂。
别人想写成:
pay(order_id="ORD123456", amount=99.9)
Python 直接拒绝——这是防呆设计!
进阶组合套路
还可以再复杂一点:
def complex_func(a, b, /, c, d, *, e, f, **kwargs):print(a, b, c, d, e, f, kwargs)
• 前两位 a, b 只能位置传参。
• 中间 c, d 可以随意。
• 后面的 e, f 必须关键字传参。
• **kwargs 捕获所有“多余”的关键字参数。
调用:
complex_func(1, 2, 3, 4, e=5, f=6, extra="hello")
输出:
1 2 3 4 5 6 {'extra': 'hello'}
非常灵活,扩展性超强!
为什么 Python 设计出这种复杂语法?
底层逻辑只有一个原因: “让函数接口不只是‘能用’,而是‘防错、优雅、可维护’。”
• 只有位置参数 —— 速度最快,适合数学计算、底层函数、内置方法。
• 可以关键字参数 —— 让配置选项清晰不易出错。
• 强制关键字参数 —— 保证易读性。
• / 和 * 配合 —— 最大化接口的安全性和美感。
这不是炫技,而是为了可读性、长期维护性服务。
大结局:如何成为接口设计高手
很多新手 Python 程序员写函数时,随便定义几个参数,默认全都能用关键字。
但真正的高手,往往会这样思考:
- 这几个参数,有没有顺序?
- 有没有不希望用户知道的参数名?
- 有没有需要显式表达、避免混淆的参数?
- 参数数量多的时候,能不能通过强制关键字传参,
让接口使用者一眼明白在做什么?
如果这些答案都有了,你就知道该在哪里放 /
,该在哪里放 *
,该不该用 **kwargs
。
总结一句话:
-
/ 和 * 是 Python 函数接口背后的“防呆装置”,
-
设计它们,是为了把“使用错误”扼杀在编译阶段。