跳转至

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 在后面很多地方还会用到;递归思想在算法课程中还会深入。