【Python编程】Python 参数传递的强制规则你必须掌握

news/2025/4/2 9:07:31/文章来源:https://www.cnblogs.com/o-O-oO/p/18802767

场景

外卖点单时,本来想在“备注”里写“不要辣”,结果不小心输错位置,填在了“地址”栏。骑手到了,给你打电话:“请问您是在‘不要辣小区’门口等我吗?”

是不是听起来有点好笑?

生活里,错误填写表单位置尚且令人哭笑不得;而在代码世界里,如果参数用错了位置,后果往往不仅仅是尴尬,还可能是致命 bug。

Python 作为一个优雅又宽容的语言,给开发者提供了极大的灵活性,尤其在函数参数传递上,你可以用位置参数、关键字参数,甚至二者混搭。

可是,你知道吗?这种“随意”,有时候会埋下隐患。

所以,Python 给我们提供了一种能力—— 可以“强迫”别人乖乖按规定方式传参:

• 有的参数只能按位置传,
• 有的参数只能用关键字传,
• 有的参数你可以混搭,但你得提前说清楚。

今天,我们就来讲讲“仅限位置参数”和“仅限关键字参数”这两个看似高级的小知识点。

放心,读完之后,你再看到/和*这两个符号,不仅不会头疼,还能体会到它们背后的设计智慧!

Python 函数参数的江湖故事

在讲“限制”之前,我们先聊聊 Python 参数传递的基本套路。

Python 函数定义的时候,参数一般有三种常见形式:

  1. 位置参数(positional arguments)

你必须按照函数定义时的顺序一个个传进去。

比如:

def greet(name, message):print(f"{name}, {message}")
greet("小明", "早上好") 

"小明"会传给name,"早上好"会传给message,非常直观。

  1. 关键字参数(keyword arguments)
    你可以显式地用参数名=值的形式指定,顺序无所谓,反正 Python 会根据参数名自动对号入座。
greet(message="晚安", name="小红")
  1. 混搭模式
    也就是有些位置参数直接传值,有些用关键字指定。
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)

瞬间世界清净了。

为什么要强制用关键字参数?

  1. 避免“把参数放错位置”这种低级错误

举个现实生活例子:
假设你开了一家进货公司,后台有一个函数:

def purchase(quantity, price):pass

有一天,一个员工写了:

purchase(10000, 4500)

意思是采购 1 万个产品,每个价格 4,500 元。

另一个员工手快打成了:

purchase(4500, 10000)

这意味着要采购 4,500 个产品,每个价格 1 万元!!
还好你后台有权限审批,不然公司资金链都断了。

如果你当初强制要求他们必须写成:

purchase(quantity=10000, price=4500)

那么这样的错误几乎不可能发生。

关键字参数就是在帮你防人类粗心的。

  1. 参数作为“可选设置”,需要显式说明

很多时候,函数有一些“默认参数”,比如 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 要有 /?

  1. 保护函数内部参数名

有时候,函数参数名只是内部实现细节,可能以后会改。
如果用户可以用关键字传参,参数名一旦改了,用户代码就会崩溃。

比如 Python 内置函数 math.pow(x, y),
你有没有发现它不能写成:

math.pow(x=2, y=3)

为什么?
因为 math.pow 是底层 C 实现,它参数名可能根本不是 x, y,Python 不希望你依赖这些名字。

换句话说:“参数名是内部机密,你只能按顺序用。”

  1. 让调用更简洁、直观

很多函数用途非常明确,参数很少,
按位置传参更干脆。

比如 min(a, b)、max(a, b)、pow(a, b),
如果一定要强制写成 min(a=1, b=2),
反而显得累赘。

有些函数天生就是:

• 参数少
• 顺序固定
• 一看就懂
不如让它简简单单用位置参数。

  1. 性能优化

位置参数解析,比关键字参数快。
底层 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 被限制只能位置传参。
这有什么用?

  1. 防止误用:有些参数不希望被用户乱命名,比如内部接口参数或者简短参数名(x, y, z)。

  2. 明确责任:必须关键字传入的参数,往往是“开关型”“配置型”的。强制你用 key=value,减少“看不懂函数参数”的尴尬。

  3. 让接口既安全又优雅:有些参数用位置传,快速方便;有些配置项用关键字传,直观易懂。

这,就是高级 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 程序员写函数时,随便定义几个参数,默认全都能用关键字。

但真正的高手,往往会这样思考:

  1. 这几个参数,有没有顺序?
  2. 有没有不希望用户知道的参数名?
  3. 有没有需要显式表达、避免混淆的参数?
  4. 参数数量多的时候,能不能通过强制关键字传参,
    让接口使用者一眼明白在做什么?

如果这些答案都有了,你就知道该在哪里放 /,该在哪里放 *,该不该用 **kwargs

总结一句话:

  • / 和 * 是 Python 函数接口背后的“防呆装置”,

  • 设计它们,是为了把“使用错误”扼杀在编译阶段。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/908746.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于FPGA的2ASK+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR

1.算法仿真效果 vivado2019.2仿真结果如下(完整代码运行后无水印):设置SNR=8db设置SNR=20db整体波形效果:仿真操作步骤可参考程序配套的操作视频。2.算法涉及理论知识概要 2.1 2ASK调制解调2ASK调制解调是一种数字调制解调技术,它是基于ASK调制的一种数字调制方式。ASK调制…

15.文件和流

本章目标Java中的文件 流 字节流 字符流本章内容 一、Java中的文件 1、文件的介绍 Java中文件操作主要通过File类来实现,File类关心的是磁盘上存储的文件File类并不是只代表文件 可以表示特定文件的名称,这里的名称就是路径 可以是某个目录。2、路径的表示方式: 因为在Java中…

16.对象流

本章目标对象流 递归(掌握)本章内容 一、对象流如果想在JVM停止后,把这些对象保存到磁盘或者通过网络传输到另一远程机器,怎么办呢?1、什么是对象流 所谓对象流也就是将对象的内容进行流化,能够输入输出对象的流称为对象流。可以对流化后的对象进行读写操作,也可将流化后…

可视化图解算法: 二叉树的中序遍历

对于二叉树的相关算法,我们总结了一套【可视化+图解】方法,依据此方法来解决相关问题,算法变得易于理解,写出来的代码可读性高也不容易出错。1. 题目 描述 给定一个二叉树的根节点root,返回它的中序遍历结果。 数据范围:树上节点数满足 0≤n≤1000,树上每个节点的值满足…

CH58x/CH59x不同类型广播使用

前言:在日常使用的时候我们用到的广播基本就是普通从机广播,在有特殊使用场景的时候我们可能会用到定向广播和拓展广播。本次对使用对定向广播和拓展广播。 一:定向广播 定向广播类型是为了尽可能快的连接,俗称回连包,这种报文包含两个地址:广播者的地址和发起者的地址。…

k 近邻算法

什么是 k 近邻? k 近邻(K-NearestNeighbor,简称 KNN)算法应该是机器学习中最简单的一个算法了,不过它虽然简单,但也有很多优点,比如:思想极度简单; 使用的数学知识很少(近乎为 0) 对于一些特定的数据集有非常好的效果; 可以解释机器学习算法使用过程中的很多细节问…

个性化配色方案

配置配色方案在app主页点击存储选择内部在根目录下找到 config 目录, 如果没有的话就创建进入 config 目录, 找到 ColorScheme 目录, 如果没有就创建进入 ColorScheme 目录, 可以创建 light.json 和 dark.json 两个文件, 一个用于在亮色模式下显示的配色方案, 一个用于暗色模式…

编辑器插件

开发编辑器插件步骤如下:在 build.gradle.kts 文件中添加 m8test sdk 依赖 , 为了减小插件apk大小, 如果是 M8Test Version Catalog 中存在的依赖库请使用 compileOnly 来依赖项目import com.m8test.util.VersionUtilsplugins {alias(m8test.plugins.android.application)alias…

基于分数Talbot效应的阵列光学涡旋产生matlab模拟与仿真

1.程序功能描述 基于分数Talbot效应的阵列光学涡旋产生matlab模拟与仿真,分别测试正方形,旋转正方形以及六边形三种阵列形状下的光学涡旋。 2.测试软件版本以及运行结果展示MATLAB2013b版本运行 测试正方形: 测试旋转正方形: 测试六边形: (完整程序运行后无水印…

组件插件

开发组件插件步骤如下:在 build.gradle.kts 文件中添加 m8test sdk 依赖 , 为了减小插件apk大小, 如果是 M8Test Version Catalog 中存在的依赖库请使用 compileOnly 来依赖项目import com.m8test.util.VersionUtilsplugins {alias(m8test.plugins.android.application)alias(m…

python-cv2读取图片位置+设置图片上文字的位置、字体、颜色等参数

import cv2 # cv2读取图片位置,图片位置必须存在桌面,与cv2的路径一样,并且图片格式和名称必须一致img = cv2.imread(face1.jpg) # POS-10-50指字的上下左右位置, font指字体,color的255指字颜色绿色pos= (10,50) font = cv2.FONT_HERSHEY_SIMPLEX color = (255,255,0) #设…

语言插件

开发语言插件步骤如下:在 build.gradle.kts 文件中添加 m8test sdk 依赖 , 为了减小插件apk大小, 如果是 M8Test Version Catalog 中存在的依赖库请使用 compileOnly 来依赖项目import com.m8test.util.VersionUtilsplugins {alias(m8test.plugins.android.application)alias(m…