Python技术迷

Python 函数参数中的反斜杠 “/ ” 是怎么回事?

你有没有在看别人 Python 代码的时候,突然看到一行:

deffunc(a, b, /, c, d):
    ...

当场一愣:这斜杠是啥?除法吗?写函数里干嘛用的?

今天就把这个事说清楚,讲完你以后再看到 / 就不会慌了,甚至还会想:嗯,这人 API 设计还挺讲究。

先看一段最简单的代码:

defadd(x, y, /, z):
return x + y + z

print(add(1, 2, 3))        # ✅ 正常
print(add(1, y=2, z=3))    # ❌ 报错

运行第二行会直接炸:

TypeError: add() got some positional-only arguments passed as keyword arguments: 'y'

重点来了:

  • / 左边的参数(这里是 x, y)叫“仅限位置参数”(positional-only)
  • / 右边的(这里的 z)是普通参数,可以位置也可以关键字

翻成人话就是:x 和 y 只能这样传:add(1, 2, 3),不能写成 add(x=1, y=2, z=3)。 但 z 无所谓:add(1, 2, z=3) 也是可以的。

Python 里函数参数,其实可以被分成三种:

  1. 只能位置传的(在 / 左边)
  2. 位置或关键字都行的(在 / 和 * 中间)
  3. 只能关键字传的(在 * 右边)

我们写个稍微全一点的例子:

defdemo(a, b, /, c, d=0, *, e, f=1):
    print(a, b, c, d, e, f)

这里参数类型是这样的:

  • a, b:只能位置参数
  • c, d:位置或关键字都行
  • e, f:只能关键字参数(因为在 * 后面)

我们试几种调用方式:

demo(1, 2, 3, 4, e=5)          # ✅ OK
demo(1, 2, c=3, e=5)           # ✅ OK
demo(1, 2, 3, e=5, f=6)        # ✅ OK

demo(a=1, b=2, c=3, e=5)       # ❌ 不行,a/b 不能用关键字
demo(1, 2, 3, 4, 5)            # ❌ 不行,e 必须写成关键字参数

你脑子里可以画一条轴:

[a, b] / [c, d] * [e, f]
  只位置     两用     只关键字

/ 就像一个“分割线”,告诉解释器——左边的参数别想用关键字来传。

为啥要搞这么个看起来多余的 /?

一开始我看到这个语法,第一反应也是: “啊这,不是给自己找麻烦吗,能用关键字传不是挺好的吗?”

后来发现主要有三大原因。

1. 帮你保护 API,不被乱用关键字

想象你写了一个工具函数:

defclamp(value, min_value, max_value):
    ...

很多人就会开始这样用:

clamp(value=10, min_value=0, max_value=100)

看起来挺优雅,但有个坑:以后你如果想把 min_value 改名成 lower,所有用你这个函数的人都得改一遍调用代码。

如果一开始你就这么写:

defclamp(value, min_value, max_value, /):
    ...

那调用只能是:

clamp(10, 0, 100)

别人根本没法用关键字参数绑死你的参数名,你以后怎么改名字都行,调用方完全不受影响。 这就是 / 的一个核心价值:保证参数位置不变就行,名称你可以以后慢慢改。

2. 跟内置函数的行为对齐

你可能见过这种提示:

help(len)
# len(obj, /)

len 的定义里,本来就是用 / 标出来的:obj 只能位置传。 像这些 C 实现的内置函数(len, range, list.append 之类),很多早就只支持位置参数了,后来 Python 把这个规则用 / 的方式“写在脸上”,也让我们自己写函数时能享受同样的设计能力。

比如模仿 pow:

defmy_pow(x, y, /, mod=None):
if mod isNone:
return x ** y
return (x ** y) % mod

调用方式会长这样:

my_pow(2, 10)           # ✅
my_pow(2, 10, mod=3)    # ✅
my_pow(x=2, y=10)       # ❌ 不行

看起来是不是就和内置函数的味道接近了?

3. 避免歧义,让签名更清晰

还有一种情况是防误用。 比如你写了一个函数:

defmove(dx, dy, /, *, relative=True):
    ...

这就很一目了然:

  • 前两个参数 dx, dy 是“位移量”,肯定是按顺序传更自然
  • relative 这种配置型参数,强制要求用关键字写出来:relative=False,可读性也好很多

如果不用 / 和 *,所有东西都随便写,很快就变成了“猜这个参数到底是干嘛的”游戏。

实战里到底要不要用 “/”?

如果你平时写的是自己用的小脚本,其实不用也无所谓。 但有几种情况,强烈推荐你考虑加上 /:

  • 这是个 给别人用的库函数 / SDK 接口
  • 你觉得以后有可能会改参数名(但不想炸掉所有调用方)
  • 你希望有些参数就是“按顺序记住”,别用关键字乱写
  • 你想让自己的函数签名看起来更严谨、更像“标准库风格”

简单给你一个“模板”,以后可以直接照着用:

defapi(value, scale, /, base=10, *, debug=False):
"""
    value, scale:只能按顺序传
    base:可位置可关键字(一般推荐关键字)
    debug:只能关键字
    """

if debug:
        print(f"value={value}, scale={scale}, base={base}")
return (value * scale) / base

最后你只要记住一句话就够了:

函数参数里,/ 左边的是“只能用位置”的, 你在设计对外 API、工具函数、库函数时,它是帮你“锁住调用方式”的一个小机关。

等你哪天在源码里看到一串:def func(a, b, /, c, d, *, e):, 心里就可以很淡定地说一句:哦,这设计还挺讲究的 😏