Kodewick
第 17 课,共 31 课目录

函数参数

第 17 课,共 31 课 · 第 3 部分 · 函数 · 约 15 分钟

第 16 课里,每个实参都必须传,而且按位置对上参数。其实还有别的传法。有几样你其实已经用过:print("09", "45", sep=":") 是按名字传 sep,不写它就用默认的一个空格。这一课讲怎么让自己的函数也有这些本事。

main.py
1def greet(name, greeting="晚上好"):2    print(f"{greeting},{name}!")3 4greet("小艾")5greet("阿本", "早上好")
输出
晚上好,小艾!
早上好,阿本!

默认值

在 def 那一行写 参数=值,这个参数就有了默认值:调用时可以不传。没有默认值的参数,每次都得传。

main.py
1def refill(lamp, liters=2):2    return f"{lamp}:加 {liters} 升"3 4print(refill("磨坊路"))5print(refill("石桥", 5))
输出
磨坊路:加 2 升
石桥:加 5 升

有默认值的参数要放在没有默认值的后面,不然 Python 分不清哪个实参该给谁。

关键字参数

调用函数时,可以写出参数的名字:liters=5。这叫关键字参数。写了名字的实参顺序随便排,调用也更好读。

main.py
1def deliver(ore, count, to="仓库"):2    print(f"{count} 块{ore}送到{to}")3 4deliver("铁矿", 12)5deliver(count=3, ore="金矿")6deliver("煤", 8, to="铁匠铺")
输出
12 块铁矿送到仓库
3 块金矿送到仓库
8 块煤送到铁匠铺

两种可以混着用,但按位置传的要放在前面。一旦有一个写了名字,后面的都得写名字。

先猜一猜

这段会显示什么?

1def box(width, height=1, depth=1):2    return width * height * depth3 4print(box(2, depth=5))

可变默认值的坑

默认值只在 Python 跑 def 那一行时做一次,不是每次调用都重新做。对数字和字符串来说这没关系;对列表就有关系了:每次不传这个参数的调用,用的都是同一个列表。

main.py
1def add_ore(item, cart=[]):2    cart.append(item)3    return cart4 5print(add_ore("铁"))6print(add_ore("金"))7print(add_ore("煤"))
输出
['铁']
['铁', '金']
['铁', '金', '煤']

本来想每次都从空车开始,结果矿越堆越多。点单步盯着 cart 看:第二次调用时,它里面已经有 '铁' 了。

常见的改法是默认值写 None,在函数里面再建新列表。cart is None 问的是「cart 是不是就是 None」,这是检查 None 的标准写法。

main.py
1def add_ore(item, cart=None):2    if cart is None:3        cart = []4    cart.append(item)5    return cart6 7print(add_ore("铁"))8print(add_ore("金"))
输出
['铁']
['金']

任意多个实参:*args

max() 和 print() 想传几个就传几个。你的函数也可以。在参数名前面加 *,它会把多出来的按位置传的实参都收进一个元组。一般叫 args,叫别的也行。

main.py
1def total_ore(*loads):2    print(f"每车:{loads}")3    return sum(loads)4 5print(total_ore(4, 8, 2))6print(total_ore(5))7print(total_ore())
输出
每车:(4, 8, 2)
14
每车:(5,)
5
每车:()
0

任意多个关键字参数:**kwargs

两个星号 ** 把多出来的关键字参数都收进一个字典:每个名字是一个键。一般叫 kwargs。

main.py
1def stock_report(place, **counts):2    print(place)3    for item, count in counts.items():4        print(f"  {item}:{count}")5 6stock_report("仓库", iron=14, copper=3, gold=1)
输出
仓库
  iron:14
  copper:3
  gold:1

参数的顺序

这几种可以写在同一个 def 里,顺序永远是这样:

顺序哪种例子
1普通参数name
2有默认值的greeting="你好"
3多出来的按位置的实参*args
4*args 后面的:只能按名字传sep=" "
5多出来的关键字参数**kwargs

*args 后面的参数只能按名字传,因为多出来的按位置的实参全被 *args 收走了。print 的 sep 和 end 就是这样的。下面这个例子用了另外四种,看看每个实参落到了哪里:

main.py
1def log(event, level=1, *details, **tags):2    print(event, level, details, tags)3 4log("delivery")5log("delivery", 2, "iron", 12, cart=3)
输出
delivery 1 () {}
delivery 2 ('iron', 12) {'cart': 3}

调用时,关键字参数后面再跟一个按位置传的,是 SyntaxError,整个程序一行都不跑:

main.py
1def deliver(ore, count):2    print(count, ore)3 4deliver(ore="铁矿", 12)
输出
  File "main.py", line 4
    deliver(ore="铁矿", 12)
SyntaxError: positional argument follows keyword argument

发生了什么: 写法不对,Python 读不懂这一行。看看括号、引号、冒号是不是成对、有没有漏。

调用时拆包

星号反过来也能用在调用里。* 把列表或元组拆开,变成一个个按位置传的实参;** 把字典拆成关键字参数。

main.py
1def deliver(ore, count, to="仓库"):2    print(f"{count} 块{ore}送到{to}")3 4order = ["铁矿", 12]5deliver(*order)6details = {"ore": "金矿", "count": 2, "to": "银行"}7deliver(**details)
输出
12 块铁矿送到仓库
2 块金矿送到银行

deliver(*order) 和 deliver("铁矿", 12) 一样;deliver(**details) 和把三个关键字参数都写出来一样。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

默认的价钱

写 price(count, each=4),返回 count * each。

要显示
12
60
exercise1.py
1# 在这里写 price()2 3print(price(3))4print(price(3, each=20))
输出
点「运行」或按 Ctrl+Enter
练习 2

每次都是新路线

这个函数掉进了可变默认值的坑。改好它,让每次调用都从空路线开始。

要显示
['磨坊路']
['集市']
exercise2.py
1def plan(street, route=[]):2    route.append(street)3    return route4 5print(plan("磨坊路"))6print(plan("集市"))
输出
点「运行」或按 Ctrl+Enter
练习 3

最长的名字

写 longest(*names),返回传进来的名字里最长的那个。

要显示
Cleo
Wick
exercise3.py
1# 在这里写 longest()2 3print(longest("Ada", "Cleo", "Ben"))4print(longest("Wick"))
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • def f(a, b=2): 给 b 一个默认值;有默认值的参数放在没有的后面。
  • 关键字参数 f(b=5, a=1) 写出参数名,顺序随便,但要放在按位置传的后面。
  • 默认值只做一次。别拿列表或字典当默认值;用 None,在函数里再建新的。
  • *args 把多出来的按位置的实参收进元组;**kwargs 把多出来的关键字参数收进字典。
  • 调用时,*列表 和 **字典 把值拆开,变成一个个实参。