Ran Wei/计算机科学系列/03
English
计算机科学基础 — Ran Wei

模块 03: 程序设计基础

编写小型目录程序,理解值、控制流、函数与错误如何影响行为。

约 5 小时4 个时段2 个实验8 道练习6 道自测题

完成后你能够

  • 跟踪赋值与分支。
  • 用列表和字典表示记录。
  • 分离逻辑与输入输出。
  • 解释别名与修改。
  • 复现并诊断错误。

开始之前

建议先修模块: 01, 02.

使用编辑器和终端运行 .py 文件;不要取 argparse.py 等与标准库相同的文件名。

目录

学习计划

5 小时

四个 75 分钟时段,包含练习。拓展任务或不熟悉的先修知识可能需要更多时间。进度本地保存,两种语言共享。

时段 275 分钟
构建与探究
时段 375 分钟
应用与拓展
时段 475 分钟
推理与复习
1

值、名称与表达式

变量名称引用值,赋值绑定名称,并非永久成立的等式。x=3、y=x、x=4 后 y 仍为 3。表达式产生值,+ 加法,== 比较,and/or 组合条件。类型决定操作意义:'3'+'4' 拼接文字,3+4 则整数相加。应明确转换外部文字并处理转换错误。

检查理解

上述赋值后 y 是多少?

完整解答

3;重新绑定 x 不会重新绑定 y。

2

选择与重复

if 选择分支,for 遍历集合,while 在条件为真时重复。Python 通过缩进组织语句。空集合使 for 执行零次。while 需要推进理由:count<3 并每次 count+=1 会结束,忘记增加则不会。break 离开循环,continue 开始下一迭代,return 离开函数;用轨迹表区分这些边界。

检查理解

真条件 while 循环缺少递增会怎样?

完整解答

没有其他退出或状态变化时,不会终止。

3

集合与修改

列表提供有序项目,字典把键映射到值。图书可表示为 {'id':1,'title':'Dune'}。多个名称可引用同一个可变对象:b=a 不复制列表,因此 b.append 会改变 a 看到的对象。a.copy 创建新的外层列表,但嵌套记录仍共享。明确函数修改参数还是返回新数据。遍历时避免改变列表长度,可构造筛选后的结果。

检查理解

复制列表会复制所有嵌套字典吗?

完整解答

浅复制只复制外层容器。

4

函数与接口

函数包含参数、函数体与结果。局部名称把临时状态限制在调用内。返回数据供调用者使用;print 产生输出,不能代替返回值。把搜索放入函数,由独立 main 解析命令行并打印。这样终端、测试和后续服务器可复用同一逻辑。__name__ 守卫让直接运行时启动接口,导入时不启动。

检查理解

为什么搜索应返回记录而非直接打印?

完整解答

不同调用者可格式化、测试或传输同一结果。

5

错误与调试

语法错误阻止解析,运行时异常表示操作失败,逻辑错误可能不抛异常却给出错误结果。从失败操作沿回溯查看调用者。用最小输入复现,说明预期,检查中间值,再修复原因。只在能够合理响应的位置捕获具体异常。用户输入应在边界验证;断言可能被禁用,不能充当输入验证。

检查理解

应使用 assert 验证用户价格吗?

完整解答

不应。明确拒绝无效输入,因为断言可被禁用。

6

常见误解

  • 浅复制不能隔离嵌套可变对象。
  • 捕获所有异常后继续可能掩盖错误状态。
7

实验准备

下载脚本,在终端中使用 Python 3.11 或更新版本运行:python m03_catalogue.py. Windows 也可使用 py -3;部分系统使用 python3。实验仅用标准库。先预测结果,再运行并完成变体。不要使用 -O,以保留断言。下方输出由构建器实际运行捕获,两种语言使用相同代码与输出。

8

实验 1 — 命令行目录

运行默认查询,再传入命令行参数 FOUNDATION。列表推导生成结果,不修改输入。

下载 m03_catalogue.py

"""Separate reusable catalogue logic from a command-line interface."""
import argparse

def find_titles(books, query):
    query = query.strip().casefold()
    if not query:
        raise ValueError("query must not be blank")
    return [book for book in books if query in book["title"].casefold()]

def main():
    parser = argparse.ArgumentParser(description="Search a small catalogue")
    parser.add_argument("query", nargs="?", default="dune")
    args = parser.parse_args()
    books = [{"id": 1, "title": "Dune"}, {"id": 2, "title": "Foundation"}]
    for book in find_titles(books, args.query):
        print(f'{book["id"]}: {book["title"]}')
    assert find_titles(books, "FOUND") == [books[1]]
    assert find_titles(books, "missing") == []

if __name__ == "__main__":
    main()
实际运行输出
1: Dune
  1. 查询不存在的书名。
  2. 传入引号包围的空白查询。
  3. 增加作者字段及独立作者搜索函数。
完整解答

缺失目标不打印记录。空白输入抛出 ValueError,否则空子串会匹配所有项。作者搜索可复用规范化规则,但读取 book['author'],验证逻辑与命令行接口分离。

9

实验 2 — 观察状态与复制

逐条预测列表。+ 表达式返回新列表,append 修改现有列表。

下载 m03_state.py

"""Observe aliasing, copies, function return values and validation."""
def add_book(books, title):
    clean = title.strip()
    if not clean:
        raise ValueError("blank title")
    return books + [clean]

original = ["Dune"]
alias = original
alias.append("Foundation")
print("after alias append:", original)
updated = add_book(original, "  Solaris  ")
print("original:", original, "updated:", updated)
assert original == ["Dune", "Foundation"]
assert updated == ["Dune", "Foundation", "Solaris"]
try:
    add_book(original, "   ")
except ValueError:
    print("blank title rejected")
else:
    raise AssertionError("must reject blank input")
实际运行输出
after alias append: ['Dune', 'Foundation']
original: ['Dune', 'Foundation'] updated: ['Dune', 'Foundation', 'Solaris']
blank title rejected
  1. 把别名赋值改为 original.copy()。
  2. 改成修改参数并更新约定与测试。
  3. 解释验证为何先于更新。
完整解答

复制后追加 Foundation 不改变 original 的 [Dune]。修改版本可 append 后返回 None,但测试应预期原列表变化。先验证可避免部分应用无效请求。

10

练习与完整解答

先尝试,再展开解答。★ 应用概念;★★ 结合概念;★★★ 进行设计或证明。

练习 1 — 赋值★

跟踪 a=2、b=a、a=a+1。

完整解答

a=3、b=2,最后仅重新绑定 a。

练习 2 — 分支边界★

对 17、18、19 求 age>=18。

完整解答

False、True、True。边界案例可区分 > 与 >=。

练习 3 — 循环总和★★

从 total=0 开始逐项累加 [2,4,6]。

完整解答

状态为 2、6、12;空列表使总和保持零。

练习 4 — 别名★★

b=a 后 b.append('B'),为什么 a 改变?

完整解答

两个名称引用同一列表。把 b 绑定到新列表不同于修改共享列表。

练习 5 — 返回与打印★★

函数打印 7 但没有 return,调用者收到什么?

完整解答

None。打印是副作用,不是返回值。

练习 6 — 空白查询★★★

为什么 strip 后验证,而非仅检查原字符串?

完整解答

空格字符串在 strip 前非空,处理后为空;应验证函数实际使用的规范化值。

练习 7 — 纯逻辑接口★★★

设计不打印、不修改输入的价格筛选函数。

完整解答

接受图书和非负整数价格上限,返回满足 b['price_cents']<=limit 的列表。需说明返回的字典仍共享,除非另外复制。

练习 8 — 调试策略★★

搜索意外匹配所有书,如何最小化调查?

完整解答

用一本书与空白查询复现,检查规范化后的查询。空子串属于所有字符串;增加边界验证与回归案例。

11

自测

选择答案查看反馈,重置后可重做。无需 JavaScript 也可阅读答案表。

1

= 做什么?

2

哪个离开函数?

3

字典是什么?

4

b=a 复制列表吗?

5

没有显式 return 时返回什么?

6

具体异常处理应放在哪里?

答案表
  1. A — 相等比较用 ==。
  2. B — return 返回结果并结束调用。
  3. C — 记录可使用命名键。
  4. A — 它共享引用。
  5. B — 打印不会自动成为返回值。
  6. C — 保留诊断信息,处理预期失败。
12

引导阅读

13

复习与下一步

不看代码解释目录的输入验证、列表表示与返回约定,增加一个失败输入测试。模块 04 将介绍论证程序所需的数学语言。

14

关键术语

术语含义
修改改变现有对象状态。
别名多个引用指向同一对象。
异常中断正常控制流的信号。