5.4 程序结构——函数¶
内容梗概¶
本课讲函数的定义与使用。从 def 关键字的基本语法和返回值讲起,说明函数存在的意义(模块化封装、实现递归);用斐波那契数列对比递归与迭代两种解法,指出递归会重复计算子问题、开销大,并顺带讲了 Python 的多元赋值/变量交换写法。然后讲 Python 特有的参数特性:参数默认值(可选参数与必选参数的声明顺序)、关键字传参、任意个数参数 *args(内部按元组处理),最后讲 lambda 匿名函数及其最经典的应用场景——作为 sort() 的 key 参数。
知识点详解¶
函数定义与返回值¶
- 语法:
def 函数名(参数):+ 冒号 + 缩进的函数体;return后跟返回值,偷懒时可直接return a+b(约 00:00–01:14)。 - 函数可以没有
return(类似 C 的 void 函数),语法上没问题,此时返回None(呼应 5.1 讲的 None)(约 02:07)。 - 为什么要有函数 (约 02:51):
- 模块化:重复使用的代码封装起来,多处调用、改一处即可,统一为程序其他部分提供服务。
- 实现递归:函数可以自己调用自己。
递归与迭代(斐波那契数列)¶
- 斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34…,每项是前两项之和 (约 03:50)。
- 递归解法:n=0 返回 0,n=1 返回 1,n≥2 返回
fib(n-1) + fib(n-2)(n 只能是整数)(约 04:14)。 - 递归的问题:计算 n 时要先算 n-1 和 n-2,而算 n-1 又要再算一遍 n-2……子问题被不断重复计算,消耗大量计算资源。凡能用递归解决的问题,基本都能找到更优秀的迭代算法 (约 08:21–10:35)。
- 迭代解法中出现 Python 特有语法:
a, b = b, a+b——逗号分隔多个变量,按位置对应赋值。Python 交换两个变量最简单的写法就是a, b = b, a,无需中间变量 (约 11:14–12:22)。
参数默认值(Python 特有)¶
- 定义时给参数赋默认值:
def countdown(start=10):,则调用时可不传参(用默认值 10)、可位置传参countdown(100)、也可“强迫症”写法指定参数名countdown(start=100)(关键字传参,对有无默认值的参数都适用)(约 12:52–17:07)。 - 没有默认值的参数不传会报错,要学会读错误信息:
missing a required argument: 'start'(约 16:20)。 - 参数顺序规则:无默认值的是必选参数,有默认值的是可选参数;必选参数必须写在前面,可选参数必须写在后面(Python 认为从第一个有默认值的参数起,后面都得是可选参数),与 C++ 一样。调用时用关键字传参则顺序可以反过来 (约 17:07–20:27)。
- 示例 countdown 还回顾了 while 循环:
while start > end: ... start -= 1实现倒数。
任意个数参数 *args¶
def 拼接(sep, *args):——参数前加星号表示该位置接收任意多个参数,想传几个传几个 (约 20:39–22:03)。- 细节:
args在函数内部被当成**元组**对待(可打印出来验证);可以用 for 循环逐个取出使用。元组与列表相似,用圆括号、内容不可变,下节课细讲 (约 22:19–23:50)。 - 示例函数顺带用了字符串的
join方法:sep.join(序列)用分隔符把序列(列表/元组)中的字符串拼接起来。
lambda 匿名函数¶
- 作用:给程序员偷懒——连函数名都不想起时,用一个 lambda 表达式定义匿名函数。语法:
lambda 参数列表: 表达式,无参数时冒号前留空 (约 24:06–27:39)。 - 示例一(回顾表白程序):按钮的
command=需要传函数,用command=lambda: print('不喜欢')代替单独定义hitme函数。lambda 本质上等价于一个 def 函数(用伪代码展开说明)。 - 示例二(最经典用法):列表的
sort()排序。列表中每项是元组时,默认按元组第一个元素排序;要按索引为 1 的元素排序,用key=lambda p: p[1]——sort 会把列表每一项逐一传给这个函数,取返回值作为排序的 key (约 29:04–31:37)。 - 对比“标准写法”:单独定义
def GetSecondItem(p): return p[1]再key=GetSecondItem,效果相同但繁琐,真实项目中大多数人都用匿名函数。 - 冷知识:语法糖——有没有这个语法都能写,但用了能尝到甜头的语法。lambda 就是语法糖 (约 33:00)。
示例与演示¶
- 求和函数:定义、调用、打印返回值;去掉 return 后打印出
None。 - 斐波那契数列递归版与迭代版对比;
a, b = b, a交换变量演示。 countdown(start=10):不传参从 10 倒数、countdown(100)从 100 倒数、countdown(start=100)关键字传参;改造成countdown(end, start=10)演示必选/可选参数顺序,反过来定义会报错。*args拼接函数:sep.join(args),打印 args 验证它是元组。- lambda:
command=lambda: print('不喜欢');sort(key=lambda p: p[1])对元组列表按第二项排序,与具名函数版本对比。
重点与难点¶
- 不写 return 的函数返回
None(与 5.1 的 None 判定联动)。 - 递归直观但开销大(子问题重复计算),要会找迭代替代。
- 参数默认值声明顺序:必选在前、可选在后,反了直接报语法错误。
*args内部是元组;要会自己打印、探索陌生对象的真实类型(培养自学能力)。- lambda 只包含一个参数列表和一个表达式,适合一次性的小函数;
sort(key=...)是其高频用法。
关联内容¶
- 前置:5.1 布尔逻辑(None、返回值判定)、5.3 循环语句(countdown 中的 while)、5.2 表白程序(command 回调函数)。
- 函数返回值与
is not None的异常处理模式见 5.1。 - 元组的详细讲解在下节课;本课结尾引出作业(Base64,见 5.6)。
- lambda 在后面很多地方还会用到;递归思想在算法课程中还会深入。