跳转至

11.1 函数进阶与类 —— 函数进阶

内容梗概

本课是入门阶段理论课的收尾部分之一,主要梳理 Python 函数在之前课程中没有讲到的进阶用法:变长参数 *args / **kwargs、函数调用时的参数解包、限定参数传递形式的特殊符号 / 和 *、以及函数标注(类型注解)。老师反复强调本课目的不是要求大家自己写代码时必须用这些技巧,而是让大家在阅读开源项目和第三方库代码、查阅 API 文档时能看懂这些写法,为后续的实验课(需要自己读文档调 API)做铺垫。课上还在 VS Code 里逐一做了演示,并顺带讲解了 VS Code 切换解释器版本与环境变量 PATH 的关系。

知识点详解

课程定位与目的 (约 00:00 - 01:50)

  • 本节课是入门阶段最后一次理论课,之后全是实践型课程(实验课会先讲原理再让学生自己动手)。
  • 本课两大主题:函数进阶 和 类。目的不是强制大家用进阶写法,而是**能看懂别人(开源项目、第三方库)的代码**。看别人的代码和能自己写代码各占 50% 的重要性。

变长位置参数 *args (约 02:20)

  • 函数定义中 *args 表示该位置可接收任意多个位置参数,这些参数在函数体内被当作一个**元组(tuple)**。
  • 例:def demo(*args): print(args),调用 demo(3, 2, 1) 打印出元组 (3, 2, 1);元素个数取决于调用时传入的个数。
  • 这是之前课程已讲过的内容,本课作为复习引入。

变长关键字参数 **kwargs (约 02:40 - 09:30)

  • 比 *args 多一个星号:**kwargs。该参数在函数体内是一个**字典(dict)**,调用时以 key=value 形式传入,key 是参数名、value 是对应的值。
  • 演示:定义含 *args, **kwargs 的函数,调用时传 a, b, c, d 四个位置参数和 name=..., age=18, gender=... 三个键值对,函数体内打印出含 4 个元素的元组和含 3 个键值对的字典。
  • 强调:这种形式在开源项目和第三方库文档中很常见,看到"函数明明只定义了两个参数(一个单星号、一个双星号)却有奇怪的调用方式"时,要能回忆起本课内容。

函数调用与参数解包 (约 09:32 - 20:50)

以求和函数 def sum(a, b): return a + b 为例讲多种调用方式:

  • 普通位置调用:sum(1, 2),a=1、b=2,返回 3。(约 10:18)
  • 关键字调用:sum(a=567, b=234),写法类似 **kwargs 的调用形式,但这里是普通参数的关键字传参;用得较少但不算错。(约 11:13)
  • 序列解包 *data:调用时(注意是调用时,不是定义时)在列表/元组前加一个星号,如 sum(*data),会把序列元素按位置拆开分别赋给 a、b。避免手写 data[0], data[1], ... 的繁琐,尤其当参数有几十上百个时。(约 12:30)
  • 字典解包 **dict:调用时用两个星号对字典解包,如 sum(**{'a': 123, 'b': 456}),字典的 key 必须与函数参数名完全一致,值赋给对应参数。(约 15:42)
  • 常见报错(要能看懂):
  • 字典 key 与参数名不一致时报 unexpected keyword argument(如把 key 改成 c)。(约 17:50)
  • 解包序列元素个数与参数个数不符时报"函数需要 2 个参数但给了 3 个"。(约 19:28)
  • 参数解包形式在开源项目中非常常见,因为大家懒得逐个索引传参。

特殊参数 / 与 *:限定传参形式 (约 21:00 - 35:50)

  • 官方文档说法:为确保可读性和运行效率,可限制允许的参数传递形式(老师吐槽这两个符号初次看毫无可读性)。
  • 语法(Python 3.8 引入):
  • 斜杠 /:其**前面**的参数**仅限位置传递**(positional-only),不能用关键字形式。
  • 星号 *(单独一个 * 占位,后面跟参数名):其**后面**的参数**仅限关键字传递**(keyword-only),必须写成 kwd=6 形式。
  • 两者之间的参数:位置、关键字均可,与未加符号时一样。
  • 演示用 pass 作函数体,逐步加 / 和 *:
  • 在 b 后加 / 后,用 a=1, b=2 关键字调用会在对应行报错:positional-only 参数却用了 keyword 传参。
  • 加 * 后,其后的参数 c 若直接按位置传 3 会报错:该参数应为 keyword argument。
  • 同时加 / 和 * 时,唯一正确的调用方式是 f(1, 2, c=3)。

现场翻车与补救:版本与解释器切换 (约 26:48 - 32:00)

  • / 和 * 语法在老师的 Python 3.7.4 上报错,查证后确认是 Python 3.8 引入。
  • VS Code 左下角可切换当前项目的 Python 解释器版本(如切到 3.8.2),但该切换**只影响运行按钮和 debug**,不影响内置终端。
  • 终端里的 python 版本只与**环境变量 PATH** 有关;改完 PATH 后要重启 VS Code(有时甚至要重启电脑)才能让内置终端加载新环境变量。替代办法:在外部 CMD 终端中运行。
  • 外部终端需手动 cd 到代码目录,VS Code 内置终端会自动切到文件所在目录,更方便。

为什么推荐用终端而不是运行按钮 (约 35:55 - 38:25)

  • 点 VS Code 的运行/调试箭头时,VS Code 会自动设置一批环境变量,可能覆盖/干扰用户自己对 PATH 的设置,排查起来复杂。
  • 终端只对应 PATH 环境变量,行为简单可控,不容易出错,所以课上一直坚持用命令行运行。

函数标注(类型注解)(约 38:27 - 44:46)

  • 动机:Python 与 C 语言不同,定义变量/参数时不声明类型;读别人代码或合作开发时,无法从函数原型看出参数和返回值应该是什么类型(函数的自解释性差)。
  • 语法(较高版本 Python 提供,非 3.8 才有):
  • 参数后加冒号和类型:def sum(a: int, b: int)
  • 参数列表后用箭头标注返回值类型:-> int
  • 完整示例:def sum(a: int, b: int) -> int: ...;字符串拼接示例 def concat(a: str, b: str) -> str: ...
  • 意义:调用者从函数原型一眼看出该传什么类型。反驳"读函数体代码也能看出类型":开源项目里一个函数可能有一两百行,从代码里推断类型非常困难,直接标注是最快的方式。这是 Python 开发团队为改进语言所做的努力。

示例与演示

  • 变长参数演示:demo(*args, **kwargs),调用时传 a b c d 和 name=安静, age=18, gender=...,打印出 4 元素元组和 3 键值对字典(VS Code 中运行验证)。
  • 解包演示:把 sum(a, b) 的多种调用方式(位置、关键字、*data、**dict)逐一 print,结果均为 801 / 579 / 579 等一致结果;随后演示两类报错(key 不匹配、元素个数不匹配)。
  • / 与 * 演示:以空函数(pass 占位)逐步添加 /、*,预测每行调用是否报错并验证:加 / 后第 7 行关键字调用报错;加 * 后第 5 行位置调用报错、第 7 行正常;两个都加后仅 f(1, 2, c=3) 正确。
  • 函数标注演示:给 sum 加 : int 与 -> int 标注,另写 concat(a: str, b: str) -> str 并 print 验证。

重点与难点

  • 定义时的星号 vs 调用时的星号:定义时 *args 是把多个位置参数"打包"成元组;调用时 *data 是把序列"解包"成多个位置参数。两者方向相反但相关,必须分清。
  • 字典解包时 key 必须与参数名完全一致,否则报 unexpected keyword argument;序列解包元素个数必须与参数个数匹配。看到这些报错要有印象。
  • / 和 * 是 Python 3.8 才引入的语法,低版本(如 3.7)会直接语法报错;VS Code 切换解释器版本不影响内置终端,终端版本由 PATH 决定。
  • 函数标注只是标注:目的是增强自解释性,方便协作开发和读文档,不是强制类型检查。
  • 本课内容记不牢没关系,关键是看别人代码时知道"原来这么回事",后面实验课查 API 文档会大量用到。

关联内容

  • 前置知识:函数基础、*args 任意长度参数(之前课程已讲)、元组与字典、环境变量 PATH 与终端(环境配置课)。
  • 与本章其他课的关系:本课是第 11 章"函数进阶与类"的第一部分,为 11.2(作用域)和 11.3(类)做铺垫。
  • 与后续实验课的关系:实验课不再逐字讲原理,需要自己读第三方库文档调 API,本课的解包、**kwargs、函数标注正是读懂文档和示例代码的必备知识。
  • 与文件目录相关课程的关联:特殊参数 /、* 的形式在之前某课贴出的列表中曾出现过。