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、函数标注正是读懂文档和示例代码的必备知识。 - 与文件目录相关课程的关联:特殊参数
/、*的形式在之前某课贴出的列表中曾出现过。