函数为什么能“看人下菜碟”?聊聊形参和实参这对黄金搭档
你有没有想过一个问题:为什么同一个函数,你塞进去不同的东西,它就能给你吐出不同的结果?
比如 print("hello") 和 print("world"),同一个 print,两次输出完全不同。再比如 len("abc") 返回 3,len([1,2,3,4]) 返回 4,同一个 len,面对不同对象给出不同答案。
这背后其实藏着一个非常精巧的设计机制——形参和实参的分离。理解了这一点,你才算真正摸到了函数的大门。
函数本质上是“模具”,不是“产品”
很多人对函数的直觉理解是:函数就是一段代码,调用它就执行。这个理解不算错,但漏掉了最关键的部分——函数是一个参数化的模具。
模具本身不生产具体产品,它只定义了“产品长什么样”。你往模具里注入不同的原料,出来的就是不同的成品。
形参,就是这个模具上的“注入口”。它规定了“这里可以注入一个东西”,但注入什么,模具自己不知道,也不关心。
def make_juice(fruit): # fruit 是形参——模具上的注入口
print(f"正在榨{fruit}汁...")
fruit 不是苹果,不是橙子,它什么都不是。它只是一个名字,一个占位符,一个等待被填充的空洞。
而实参,就是你真正注入的原料:
make_juice("苹果") # "苹果" 是实参——你注入的真实原料
make_juice("橙子") # "橙子" 是实参——换个原料,产出就变了
形参定义的是“结构”,实参提供的是“内容”。 这就是函数能“看人下菜碟”的根本原因。
一道题检验你的理解
在Python函数定义中,用于接收调用时传入数据的变量被称为( )。 A. 实参 B. 形参 C. 返回值 D. 局部变量
这道题看似简单,但选项设计得很有讲究,每一个干扰项都对应着一种常见的认知偏差。
正确答案是 B(形参)。
为什么不是 A(实参)?因为题目问的是“函数定义中”用于接收数据的变量。定义阶段出现的是形参,调用阶段出现的才是实参。一字之差,方向完全相反——形参是“接收方”,实参是“给出方”。
为什么不是 C(返回值)?返回值是函数往外送的东西,方向反了。形参是往里收,返回值是往外送,一个是入口,一个是出口。
为什么不是 D(局部变量)?这个选项最有迷惑性。形参确实是一种特殊的局部变量,但题目强调的是“接收调用时传入数据”这个特定功能,只有“形参”这个词才精确对应。就像“苹果”是一种“水果”,但题目问的是“哪种水果是红色的、脆的、常用来做派”,答案当然是“苹果”而不是“水果”。
这道题的核心考点就一句话:定义时是形参,调用时是实参。
位置、关键字、混用——传参的三种姿势
理解了形参和实参的本质区别,接下来就要搞清楚实参怎么传给形参。
第一种:位置实参。 按顺序一一对应,就像排队上车,谁站前面谁先上。
def buy_snack(name, price, count):
total = price * count
print(f"买了{count}包{name},共{total}元")
buy_snack("薯片", 5, 3) # name="薯片", price=5, count=3
这种方式的隐患是:参数一多,你很容易搞混顺序。buy_snack(5, 3, "薯片") 也不会报错,但结果完全不对——Python 不会帮你检查语义,它只认位置。
第二种:关键字实参。 指名道姓,顺序随便。
buy_snack(count=2, name="巧克力", price=8) # 顺序打乱也没问题
关键字实参的好处不只是“顺序灵活”,更重要的是代码的自解释性。make_student("小明", 10, 5) 和 make_student(name="小明", age=10, grade=5) 哪个更好读?答案不言自明。
第三种:混用。 位置实参必须在关键字实参之前。
buy_snack("饼干", count=4, price=3) # 正确
# buy_snack(count=4, "饼干", price=3) # 语法错误
为什么?因为 Python 需要先通过位置确定哪些形参已经被填充,再用关键字去匹配剩下的。如果位置实参出现在关键字后面,Python 就无法判断这个位置值该给谁。
一个判断题暴露的认知盲区
在Python中,函数调用时,实参的个数必须与形参的个数完全一致,否则会报错。
这句话对吗?
不对。 但这个“不对”有个前提条件——只有在函数使用了默认参数或可变参数时,实参个数才可以和形参不一致。
def greet(name, greeting="你好"): # greeting 有默认值
print(f"{greeting},{name}")
greet("小明") # 只传一个实参,greeting 用默认值
greet("小明", "早上好") # 传两个实参,覆盖默认值
这个判断题容易出错的根源在于:很多人从 C 或 Java 转过来,那些语言确实要求参数个数严格匹配。但 Python 的动态特性允许更灵活的设计。默认参数、*args、**kwargs,这些都是 Python 在参数机制上的“超能力”。
形参和实参,本质上是一种“契约”
回到最开始的问题:函数为什么能看人下菜碟?
因为形参和实参的分离,本质上是在函数和调用者之间建立了一份契约。
函数说:“我不管你传什么,但你得按我规定的格式传。我定义了几个形参,你就得给我对应数量的实参(除非我有默认值)。”
调用者说:“行,我按你的规矩来。但我传什么值,是我的自由。”
这份契约让函数变成了一个可复用的逻辑单元——逻辑不变,数据可变。这正是编程中最核心的抽象思想之一。
理解了形参和实参,你接下来就可以自然地理解默认参数(给契约加个“可选条款”)、可变参数(让契约更灵活)、甚至装饰器(在契约外面再包一层契约)。这些高级特性的根基,都在今天聊的这对搭档身上。
多写几个带参数的函数,刻意练习位置传参和关键字传参的切换,你会慢慢体会到:好的函数设计,就是设计一份清晰的契约。
关于作者
我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。
这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。
如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)