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() 就是教科书级示范——sep、end 都有默认值,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 变量作用域