跳到主要内容

5.2 函数参数

上一节的参数只有一种玩法:按顺序传。但你其实见过更高级的用法——2.4 节的 print("a", "b", sep="-")sep="-" 是什么写法?print 为什么想传几个值就传几个?这一节把参数的几种形态一次讲清,它们都是为了让函数更好用、更不容易用错

位置参数:按顺序对号入座

默认方式,实参按位置依次分给形参:

def introduce(name, age):
print(f"我叫{name},今年{age}岁")

introduce("小明", 18) # name="小明",age=18

位置参数的弱点:顺序传反了不报错,逻辑却错了

introduce(18, "小明") # 我叫18,今年小明岁 ——程序不崩,话说得离谱

参数一多这个问题会放大:create_user("tom", 25, 1, 0, 1) 后面三个 1、0、1 是什么鬼?谁也看不懂。

关键字参数:指名道姓地传

调用时写 参数名=值,明确指定每个值给谁——顺序随意,可读性飙升:

introduce(age=18, name="小明") # 我叫小明,今年18岁——顺序反了也没事

2.4 节的谜底揭晓:print("a", "b", sep="-") 里的 sep="-" 就是关键字参数。

两种方式可以混用,但有一条铁律:位置参数必须全部在关键字参数前面

introduce("小明", age=18) # ✅ 常见搭配:主要的按位置,次要的指名
introduce(name="小明", 18) # ❌ SyntaxError: positional argument follows keyword argument

道理很简单:一旦开始指名道姓,后面再冒出一个"无名"的值,Python 就不知道它该给谁了。

默认参数:可传可不传

定义时用 参数=默认值 给参数设置"出厂设置",调用时不传就用默认的:

def greet(name, greeting="你好"):
print(f"{greeting}{name}!")

greet("小明") # 你好,小明! ← 没传 greeting,用默认值
greet("小明", "早上好") # 早上好,小明! ← 传了就覆盖

默认参数的意义:让常见用法保持简洁,同时保留定制的余地print() 就是教科书级示范——sepend 都有默认值,90% 的场景直接用,偶尔需要才指定。

定义时也有一条铁律:有默认值的参数必须排在没默认值的后面

def greet(greeting="你好", name): # ❌ SyntaxError: parameter without a default follows parameter with a default

原因同样是"对号入座"会乱:如果允许这么定义,greet("小明") 里的"小明"该算 greeting 还是 name?Python 干脆从语法上禁止。

*args:想传几个传几个

print("a", "b", "c") 传 3 个行,传 10 个也行——它怎么做到参数个数不限的?答案是可变参数:参数名前加一个 *,多余的位置参数会被打包成元组塞给它:

def add_all(*nums):
print(nums) # 看看它到底是什么
total = 0
for n in nums: # 元组可以遍历(4.2 学过)
total += n
return total

print(add_all(1, 2)) # 先打印 (1, 2),返回 3
print(add_all(1, 2, 3, 4)) # 先打印 (1, 2, 3, 4),返回 10
print(add_all()) # 先打印 (),返回 0——一个不传也行

名字 args 是 arguments 的缩写,社区习惯叫法,换成 *nums*values 都行——起作用的是那个 *

💡 配套地还有 **kwargs:两个星号,接收任意多个关键字参数,打包成字典

def show_info(**kwargs):
for k, v in kwargs.items():
print(f"{k}: {v}")

show_info(name="小明", city="上海") # name: 小明 / city: 上海

新手阶段的任务是见到 *args, **kwargs 能认出来(阅读别人代码时高频出现),自己写暂时用不上,不必深究。

四种参数总览

形态写在哪例子解决什么问题
位置参数调用时f(1, 2)最基本的传值
关键字参数调用时f(x=1, y=2)顺序易错、可读性差
默认参数定义时def f(x, y=0):常用值不想每次都传
可变参数定义时def f(*args):参数个数不确定

新手大坑:默认值千万别用列表

这是 Python 面试都爱考的经典坑。看这个"每次给购物车加一件商品"的函数:

def add_item(item, lst=[]): # ❌ 危险写法:默认值是列表
lst.append(item)
return lst

print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['苹果', '香蕉'] ??上一次的苹果怎么还在!

两次调用明明都没传 lst,应该各自得到全新的空列表才对,怎么串场了?

原因要用 4.1 的"贴标签"知识来解释:默认值 [] 只在 def 执行时创建一次,之后所有不传 lst 的调用,拿到的都是同一个列表的标签。第一次调用往里塞了苹果,第二次调用接着往同一个列表里塞香蕉——就像餐厅只有一个公共餐盘,每桌客人都往里夹菜。

数字、字符串当默认值为什么没事?因为它们不可变(2.3、4.2 讲过),谁也没法"往里塞东西",自然不会串场。

正确写法——用 None 当哨兵,真正的空列表在函数每次执行时新建:

def add_item(item, lst=None):
if lst is None: # 没传列表?现场造一个新的
lst = []
lst.append(item)
return lst

print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['香蕉'] ✅ 各是各的

💡 is None 是判断"是不是 None"的标准写法(比 == None 更规范)。结论请背下来:默认值只用数字、字符串、True/False、None 这类不可变的东西;列表、字典想当默认值,一律用 None 替身。

练习

新建 practice52.py 完成:

1. 写函数 power(base, n=2):计算 base 的 n 次方。验证 power(3) 是 9、power(2, 10) 是 1024。

2. 写函数 make_tea(tea, size="中杯", sugar="半糖"),打印"一杯{size}{sugar}的{tea}"。要求用三种方式各调用一次:纯位置参数、纯关键字参数、只改 sugar 不改 size。

3. 不运行,先猜每行结果(正常输出?报错?),再验证:

def f(a, b=1, c=2):
print(a, b, c)

f(10)
f(10, c=30)
f(b=5, 10)
f()

4. 写函数 count_max(*nums):返回传入所有数字中的最大值(不用内置 max,用循环打擂台)。

点击查看答案
# 1
def power(base, n=2):
return base ** n

print(power(3)) # 9
print(power(2, 10)) # 1024

# 2
def make_tea(tea, size="中杯", sugar="半糖"):
print(f"一杯{size}{sugar}{tea}")

make_tea("乌龙茶", "大杯", "全糖") # 纯位置
make_tea(tea="乌龙茶", size="大杯", sugar="全糖") # 纯关键字
make_tea("乌龙茶", sugar="无糖") # 跳过 size 只改 sugar——关键字参数的价值

第 3 题:

  • f(10)10 1 2(b、c 用默认值)
  • f(10, c=30)10 1 30(跳过 b 指定 c,全靠关键字参数)
  • f(b=5, 10) → ❌ SyntaxError(位置参数不能跟在关键字参数后面)
  • f() → ❌ TypeError: missing 1 required positional argument: 'a'(a 没有默认值,必须传)
# 4
def count_max(*nums):
biggest = nums[0] # 拿第一个当擂主
for n in nums:
if n > biggest:
biggest = n # 更大的上台
return biggest

print(count_max(3, 9, 2, 7)) # 9

本章小结

  • 位置参数按顺序、关键字参数按名字;混用时位置在前
  • 默认参数 def f(x, y=默认值) 让常见调用更简洁;有默认值的排后面
  • *args 收任意个位置参数(元组),**kwargs 收任意个关键字参数(字典)——先混脸熟
  • 默认值别用列表/字典:def 时只创建一次、所有调用共享;用 None + 函数内新建

下一节:5.3 变量作用域