跳转至

2022-回放-03:作业讲评、生成器、二进制数据与第二次作业 Base64(第三次直播回放)

内容梗概

这是第三次直播回放课(约 1 小时 52 分钟),内容是"第一次作业讲评 + 程序结构章节补充 + 布置第二次作业"。老师先随机点评了几位同学的 ROT13 作业(借函数参数的位置/关键字调用讲评写法),随后现场用自己写的测试脚本批量测试全班作业(还闹了测试条件写反的"翻车")。之后补充讲解程序结构章节的遗留内容:in 关键字、循环控制、pass 的开发占位用法,并重点引入生成器(yield)与列表的本质区别、bytes 二进制数据类型、用字典替代 switch 实现多分支。最后布置第二次作业:从零实现 Base64 编码解码并自带测试。结尾预告了后续课程安排(数据类型进阶 + 输出合并、字符画实验、加快看视频进度)。

知识点详解

函数参数的调用方式(约 00:00–03:00)

  • 以带一个必选参数 + 三个默认参数(xxx=... 形式)的函数为例,讲解三种调用方式:
  • 只传必选参数,可选参数全用默认值;
  • 指名道姓用关键字传参(参数名=值);
  • 按位置顺序传参。
  • 规则:关键字参数必须跟在位置参数后面。一旦某个参数用了 名字=值 的形式,它后面的参数也必须用关键字形式;位置传参就要全部按顺序。可选参数的默认值写函数定义里,调用时按位置传可省略 base=。
  • 讲这个的原因:将来读别人代码会遇到各种"稀奇古怪"的合法写法,要看得懂。

第一次作业讲评(约 03:00–12:00)

  • 随机抽同学代码点评,共性问题:
  • 代码格式:操作符(如 ,、=) 两侧要空格;顶格语句该顶格;合理空行。
  • if (条件): 加括号是 C 程序员遗留习惯,Python 里不需要(但也能正常工作)。
  • 字符串引号风格统一问题(承接上节课)。
  • 好的写法示例:decode 里用 for 循环 + 切片 s[i:i+2] 每次取两个字符、i += 2 步进;int(x, 16) 转十六进制。
  • 老师现场用自动测试脚本批量测全班作业:随机生成多组数据逐一比对,打印错误项——结果"全军覆没",排查后发现是**老师自己测试脚本的判断条件写反了**(在相等时打印错误),改正后全部通过。教训:测试代码本身也可能错;也顺便演示了做日志时应把错误样例打印出来。
  • 小技巧:网页内容禁止复制时,右键"检查"在开发者工具里选中 HTML 直接复制。

程序结构章节补充(约 12:30–28:00)

  • 开场示例:一个"点不到的表白按钮"小程序(你鼠标永远点不到"不"),用到循环不断生成随机位置 + 分支,以及 Python 内置 GUI 库(tkinter);说明 Python 写带界面的程序很简单,代码细节自己查文档。
  • in 关键字在分支判断中的用法(复习):if name in student_list:,后面可以是列表、字符串、字典、元组等序列,用于成员查找;经典算法题"长字符串中查找子串"也可用 in。
  • Python 没有 do while(C 语言有),只有 while;两者相似,只是条件位置前后不同,改写很容易。
  • break/continue/else 与 C 语言类似不展开。
  • pass 的开发用法:先把程序框架(函数名、流程)搭好,未实现的函数体写 pass 占位避免报错,之后逐个填充并测试。这是正式开发中非常常见的流程——上节课作业的"完形填空"框架就是这个用法的实例。
  • 老师强调"开发能力"的重要性:信安专业同学专业课强但开发常欠缺,而行业越来越多工具/任务要自己开发,不能只学语法,要学会"设计 → 拆解 → 搭结构 → 填充 → 测试"的完整开发思路。

生成器(generator)与 yield(约 28:00–46:20)

  • 以 def reverse(data): 为例:把一个序列倒序输出。循环写法很巧妙——for index in range(len(data)-1, -1, -1):(start 为最后一个下标=长度-1,end 写 -1 才能取到下标 0,step 为 -1 倒着走),每次 yield data[index]。
  • 用 yield 返回的"函数"叫**生成器**。老师本想强调"生成器不是函数",现场 print(type(...)) 验证时翻车:含 yield 的定义**确实是函数**(type 是 function),但它的**返回值类型是 generator**;而普通函数返回什么类型就是什么类型(如 list)。结论:函数是个大集合,生成器是一种特殊的函数(返回生成器迭代器的函数),详细内容在"函数进阶与类"章节展开。
  • 生成器与列表的核心区别:列表在创建时就初始化、占好全部内存(如存 1 到 1024 每个元素 2 字节,空间已分配);生成器返回时并没有算出所有元素,在你迭代循环的过程中才逐个计算——是节约运行时内存的手段。
  • 应用场景:内存受限的嵌入式/板卡开发(树莓派等,板卡可能只有 16KB 内存),要生成 0 到一千万的序列时必须用生成器而非列表。普通电脑开发对此不敏感。
  • 又一次翻车:range 也不是生成器——type(range(10)) 显示它是一个单独的 range 类(可迭代对象),与生成器无关。老师借此灌"终生学习、学了忘忘了学"的鸡汤,并提醒学生要敢于质疑老师、一切用代码跑一遍验证。

二进制数据:bytes 与 bytearray(约 57:40–1:16:00)

  • 为什么讲:Base64 既能处理文本数据也能处理二进制数据(如 0x00 这种在字符串里无意义的字节,在二进制视角下与其他字节完全平等),所以作业要求用随机二进制序列做测试。
  • Python 的序列分**可变序列**(列表,可修改元素)和**不可变序列**(元组,元素不可改)。"数据类型进阶"一章会系统讲。
  • bytes 对象:**不可变**的字节序列;想修改就用 bytearray(可变字节序列)。
  • bytes 与字符串表面很像,本质是两种东西。演示:用 open("1.png") 文本模式读图片文件报 UnicodeDecodeError——Python 3 默认把读入的二进制用 UTF-8 解码成字符串,而图片里大量字节无法解码(人类可读字符有限、字节组合无限)。二进制视角讲解:Unicode 是字符集,UTF-8 是它的一种编码方式。
  • 正确做法:open("1.png", "rb") 以二进制模式读,f.read(10) 读 10 字节,得到 bytes 对象。
  • bytes 打印出来的两种形态:能解码成可打印字符的直接显示字符(如 P、N、G,以及 \r 回车、\n 换行这类 ASCII 控制符);解码不出来的显示十六进制转义(如 \x89)。文件操作与编码细节在"文件与目录"章节展开。
  • 随机生成二进制序列:random.randbytes(n) 生成 n 个随机字节(random 模块接口)。

字典与多分支(Python 为什么没有 switch)(约 1:22:50–1:43:00)

  • Python 没有 switch/case 语句。以"成绩等级"为例:60 以下不及格、60–80 中、80–90 良、90 以上优,用 if/elif 嵌套写很啰嗦;input("请输入...") 函数接收用户输入(类似 scanf)。
  • 改用字典:字典是 key: value 形式的数据类型,定义好规则字典后,用 key 取 value 一句话替代多重分支(如 grade 为 key、分数区间描述为 value)。前提是要先定义好这个映射字典,且"得到 key 的过程"不能太复杂(分数区间比较这种就不适合直接用字典做 key)。
  • 字典的 key 必须是不可变对象:字符串、数字、元组、bytes 都可以;列表、bytearray 是可变的,不能做 key。value 可以是任何对象:数字、列表、另一个字典、自定义类的实例都行,"包罗万物"。字典可以有 0 组、1 组或多组 key-value。
  • 字典火的原因——JSON(JavaScript Object Notation):起源于 JavaScript、如今 Web 领域最通用的数据传输格式,本身就是 key-value 字典形式。现场用浏览器开发者工具演示:知乎页面下翻加载新内容是通过 fetch/xhr 请求向服务器要数据,返回的基本都是 JSON(能看到 data、paging 等 key)。Python 自带 json 库,一句话就能把 HTTP 拿到的 JSON 转成字典(后话)。
  • 由"用字典改写分支"引申:数据结构课程的意义——数据结构为程序服务,选对结构能改变代码的整体逻辑,让程序更工整明晰。

示例与演示

  • 作业讲评演示:逐份点评同学 ROT13 作业的代码风格与写法;现场运行自动测试脚本(随机生成数据 → encode → decode → 比对,统计对错数量),并现场修复测试脚本自身的 bug。
  • reverse 生成器演示:对比"yiled 版"与"append 拼列表版"两种写法,type() 验证返回值类型分别为 generator 和 list;type(range(...)) 验证 range 是独立类。
  • bytes 演示:同一段 "ABC" 分别存为字符串和 bytes 对比;open("1.png") 文本读报错 → 改 rb 成功读出,逐字节讲解 \x89、PNG、\r\n 的显示形态。
  • 字典替代 switch 演示:input 输入等级 → 字典 key 取 value 输出对应分数区间(中途发现"分数→等级"方向不适合用字典,及时换成"等级→区间"方向)。

第二次作业:手写 Base64 编码与解码(约 47:50–58:30,1:16:00–1:22:00)

  • 背景:Base64 是经典编码算法(CTF 常见),原理在课程视频里已讲,本课不再重复,自己回去看视频。算法本身要考虑的分支和异常处理很精巧,值得亲手实现。
  • 作业要求(比第一次作业提高一档:空仓库、无模板代码,全自己写,且首次对仓库内容提出明确要求):
  • fork 老师建的空仓库,只提交**一个 .py 文件**:不要用中文命名、不要没有后缀名,建议叫 homework.py;不要叫 base64.py——会与 Python 内置的 base64 库冲突导致奇怪问题。
  • 文件里实现两个函数:encode 和 decode。
  • 必须自己写测试:在 if __name__ == "__main__": 下写循环,随机生成**二进制序列**(如 random.randbytes(random.randint(10, 20)))→ 丢进 encode → 再 decode → 比对与原始输入是否完全一致,全部通过才算成功。老师拿到代码会直接 python homework.py 跑测试并核对对错数量。
  • 接口约定:encode 输入 bytes、输出 str;decode 输入 str、输出 bytes。输入 bytes 经编码解码后应还原出相同的 bytes。
  • 易错点——补等于号:待编码字节数不是 3 的倍数时,编码结果末尾必须补 =(可能一个或两个)。往年很多同学漏补,导致解码出错,本次强制要求。
  • 严禁使用内置 base64 库,必须自己实现算法,否则作业没意义。
  • 提醒:不要去找上一届同学的作业抄(内容与上届相同,但要求不同),后续实验作业(找文件特征、博客开发、爬虫等)都会换题。

重点与难点

  • 位置参数与关键字参数的混用规则(关键字必须在位置之后)。
  • 生成器的本质:含 yield 的也是函数,但返回 generator 对象;惰性计算省内存,与列表的本质区别在于是否一次性初始化。
  • range 是可迭代的独立类,不是生成器也不是函数。
  • bytes vs str:文本模式读二进制文件会因 UTF-8 解码失败报 UnicodeDecodeError;读二进制一律 "rb"。
  • 字典 key 的可变性限制(不可变对象才能做 key),这是常考易错点。
  • Base64 作业三大坑:文件名撞库、漏补 =、没有覆盖二进制数据的测试。
  • 测试代码也要验证(老师的测试脚本条件写反,差点"误杀"全班)。

关联内容

  • 承接回放-02:讲评的正是第一次作业(ROT13 + ASCII 十六进制);in、列表推导式、if __name__ == "__main__" 等均是上节课内容的复习与应用。
  • 生成器、迭代器的系统讲解在第 11 章"函数进阶与类";编码/字符集、文件读写(open、rb、encoding)在"文件与目录"章节展开。
  • 字典、元组、集合、range 的系统讲解在"数据类型进阶"视频(下下周与"输出"章节合并上线下课);JSON 与字典的转换、fetch/xhr 分析为后面"爬虫"章节(分析知乎接口)做铺垫。
  • 预告:后续课程加快看视频进度(可两倍速/快进),第 7 章起内容更偏实战,线下课将做"图片转字符画(ASCII 图)"等小实验——用本阶段所学知识即可完成。
  • 前置知识:C 语言的循环/分支/函数基础、ASCII 码与进制转换(C 语言课已学)。