前言
同学们是否听说过“编程”这个看起来很厉害的东西,但又不知道它到底是什么? 是否因为看到过电影里的黑客对着黑色的屏幕敲了一串字母,最后酷炫地按下回车键,从而对计算机的世界感到好奇?
在本次实验中,我们将会用Python3这个编程语言为主来教会大家如何编写代码、如何运行代码,以及教大家如何像黑客一样对着你的电脑键盘一阵“乱敲”,然后按下回车键耍帅! 当然,我们还需要教会你一些不那么酷炫的事情——如何完成作业以及提交作业。
关于实验讲义
实验讲义随实验内容一起发布。实验讲义将会(努力)指引你完成实验内容,请仔细阅读。
在开始之前,我们先介绍一下怎么阅读实验讲义。
浏览
- 点击左侧的目录可以在章节间进行跳转。
- 按下键盘的右方向键,或点击页面右侧的箭头浏览下一页。
- 按下键盘的左方向键,或点击页面左侧的箭头返回上一页。
约定
在第一次实验讲义中我们会用一些约定俗成的格式来丰富讲义内容。 下面的例子仅用于说明各种格式的含义,如果内容暂时看不懂也没有关系。
-
加粗文本通常表示被强调的重要内容。如:请认真学习并严格遵守学术诚信
-
斜体文本通常表示术语。如:黑客指水平高超的电脑高手
-
蓝色文本指向可以点击进行跳转的网页。如:这是今年的课程主页
对于网页链接你可以
- 用鼠标右键点击以复制网页地址
- 用鼠标左键点击以在当前标签页打开
- 或者,按住Ctrl键的同时用鼠标左键点击以在新标签页打开
- 或者,用鼠标左键拖动蓝色文本至浏览器标签栏以在新标签页中打开
-
代码块里的内容通常和代码相关。如:
- 表达式
5 * 4 * 3 * 2 * 1
计算了5的阶乘 - 下面这段 Python 代码打印了值
2024
print(2024)
- 下面的代码块展示了助教在输入
python
命令后,终端里的内容sicp@pascal02:~$ python Python 3.8.10 (default, Sep 11 2024, 16:02:53) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>
- 表达式
学习指南
学会如何提问
大学生活和高中差别可大了,大家只有学会自己学习、学会向他人提问才能轻松掌握知识。 能够学会提问不仅能让大家更快解决你的问题,还能让大家更乐意帮助你。
在这里向大家推荐两个必读的传世经典:
知道你不会看的(我也没看),总之就是:
- 首先尝试去独立解决问题,自己先思考再提问;
- 尝试自己搜索问题的答案;
遵守学术诚信
学术诚信是大学教学的重要部分,需要大家认真学习并严格遵守。
本课程鼓励大家通过QQ群提问等方式互相交流,禁止包括但不限于下述违反学术诚信的行为:
- 复制粘贴网上找来的代码
- 展示自己的代码给别人抄
- 直接提交别人的作业
- 在考试过程中作弊
根据《南京大学学士学位授予管理办法》,因违反学术诚信受到记过(含记过)以上处分者,不得授予学士学位。 学习并遵守学术诚信是一件非常严肃的事情,请大家务必认真对待。
环境配置
工欲善其事,必先利其器。
在本课程的实验中,我们用 Python3 这个编程语言来教大家编程。 要用它进行编程,需要
- 在电脑上安装 Python3
- 在电脑上安装编写代码的工具
在这一章节我们介绍 Python3 和代码编辑器 VSCode 的下载和安装。
目前最主流操作系统有 Windows 系统、macOS 系统和 GNU/Linux 系统。 考虑到同学们可能使用各种不同品牌的电脑与操作系统,本章节会兼顾这三种主流操作系统。 若在按照实验讲义操作的过程中有难以解决的问题,欢迎大家在实验课上或课程群中随时提问。
Python3
Python3 是一门有趣的编程语言,学会它可以做一些很酷的事情,包括但不限于训练 AI、制作游戏、成为黑客等等。 大家平时可能听得比较多的说法是 Python,而不是 Python3。 实际上,Python3 表示 Python 这个语言的“3号版本”。
有是有,不过这是1991年发布的东西,现在可能已经见不到了。 Python3 和 Python2 有很多不同的地方。但 Python3 比 Python2 更新,也更主流(Python2 在2020年终止支持),因此我们的教学也采用了 Python3。 在实验讲义和以后的课程中,如果我们没有明确说明,那么 Python 指的都是 Python3。
编辑器?
本质上,代码就是一堆文本。所以理论上我们使用 Windows 自带的记事本就可以写代码了。
你可以用记事本打开实验材料里的 hello.py
试试(在 Windows 系统中,请右击文件,在“打开方式”中选择“记事本”,请勿直接双击文件)。
如果一切顺利,你将会看到如下内容:
2024
print(2024)
大家可以试着在记事本里编辑代码,例如把“2024”改成“2025”,或者是你喜欢的任何数字,然后保存。
我们把像 Windows 上的记事本一样的这类软件称作文本编辑器(Text Editor)。 但是闻道有先后,术业有专攻。作为一名专业的(预备役)程序员,我们最好还是用专业的编辑器。 VSCode就是目前最为流行的编辑器之一!
狭义地说不算。word 做的事情比文本编辑器更多,你可以用 word 调整格式、修改字体、绘制图案。 为了实现这些复杂的功能,word 文件通常会携带许多额外内容。
Windows
安装Python
在浏览器中访问https://www.python.org,点击下方的“Download”中的最新版本。
安装VSCode
在浏览器中访问https://code.visualstudio.com,点击下载按钮下载安装程序。
macOS
安装 Python
macOS自带 Python。
安装VSCode
在浏览器中访问https://code.visualstudio.com,点击下载按钮。
你将会得到一个压缩包,将压缩包解压后出现一个VisualStudioCode.app
文件,将这个文件拖拽到访达(Finder)的“应用程序”中即可。
GNU/Linux
安装Python
Ubuntu自带Python。
安装VSCode
在浏览器中访问https://code.visualstudio.com,点击下载按钮。
你将会得到一个.deb
文件,在终端中使用命令 dpkg
安装该软件包。
$ sudo dpkg -i ./code_xxx.deb
小结
到现在,同学们应该已经完成了以下内容:
VSCode 基础
要学习如何编写程序,首先要知道如何使用编辑器和开发环境。 这些操作不仅将在本课程中被广泛用到,还会伴随同学们完整的程序员生涯。
在本章中,我们将学习如何使用 VSCode 来
- 浏览和编辑文件
- 安装插件
- 执行简单的命令
认识VSCode
启动我们刚刚安装好的 VSCode,你将会看到类似的界面(可能是黑色的,可能是英文的):
打开文件夹(推荐)
VSCode 可以打开文件夹,从而浏览和编辑文件夹内的所有文件。 在做实验的过程中,我们推荐每次都用 VSCode 打开包含下载代码的文件夹。
打开文件夹有两种方式:
- 打开 VSCode,再点击左上角菜单中的“文件”——“打开文件夹”,我们就可以选择一个文件夹。
- 在文件资源管理器中,(对文件/文件夹/空白处)右键应能看到“通过Code打开”,点击即调用 VScode 打开对应文件/文件夹。
以助教的电脑和项目为例,打开文件夹的操作完成后 VSCode 将如下图所示:
此时 VSCode 的界面主要包含:
- 其它菜单:提供文件搜索、文件浏览、插件管理等功能
- 文件管理:当在其它菜单选中“文件”时(示例图中的第一个小图标),我们可以在文件管理区查看打开文件夹中的所有文件
- 代码编辑:在文件管理区中点击文件即可打开编辑
打开文件
VSCode 也可以浏览和编辑单一文件。 只需要点击左上角菜单中的“文件”——“打开文件”,我们就可以选择一个文件打开进行编辑。
其余的部分和打开文件夹类似,请自行探索一下。
文件的保存
就如同大家写 word 文档时需要保存一样,在 VSCode 写代码时也需要及时保存。
当你看到下图中被红框框起来的白点时,代表这个文件需要被保存了。 你可以通过左上角菜单中的“文件”——“保存”来保存文件,或者按下键盘上的 Ctrl 键和 S 键来保存文件。
配置VSCode
VSCode 的很多额外功能都是通过插件(Plugin)来实现的,正是这些丰富而强大的插件让 VSCode 非常流行。
这一章节我们介绍一些助教常用的插件。
安装中文语言插件
中文语言插件并非必须项。 如果你不习惯英文,那么可以安装中文语言包。
安装 SICP插件
安装插件并非难事,如果觉得棘手,就回头看看语言包的安装流程吧。扩展商店里的插件一般都会标注作者和组织,你可以根据这个简单判断是否找到了正确的插件。
安装 Python 开发套件
Python 开发套件默认已经安装。
其它插件
在 VSCode 中执行命令
打开终端
这个小框框就是终端,也是大家在影视作品中见到的黑客天天打交道的东西。
执行指令
你将会看到终端里打印了一串东西——“Python 3.12.3”。
这串东西的含义是,助教电脑上的 Python 版本是 3.12.3
。
回忆一下,是不是和你刚才安装的 Python 版本一样呢?
小结
到现在,同学们应该已经完成了以下内容:
Python 基础
我们已经了解了如何打开终端并执行命令。接下来,我们就可以正式开始学习 Python 了!
Python 解释器(Interpreter)提供两种方式来运行代码。
- 交互模式(Interactive mode):用户(也就是你)每输入一行代码,Python 就会立即执行刚刚输入的代码
- 脚本模式(Script mode):用户提供写好代码的文本文件,Python 一次性执行其中的所有代码
在本章中,我们首先使用 Python 解释器的交互模式来快速尝试各种代码。 然后,我们教大家在文件中编写 Python 代码,并使用 Python 解释器执行它。
Python,启动
刚才我们说过,Python 解释器是用来执行 Python 代码的软件。 Python 的交互模式能够让我们一边输入代码,一边查看代码的执行结果——这样可以快速尝试,得到反馈,是很好的学习 Python 的做法。 我们来看看如何启动 Python 解释器的“交互模式”。
VSCode 终端(推荐)
IDLE
IDLE(Integrated Development and Learning Environment)是 Python 自带的交互环境。
认识交互界面
如果一切顺利,你会发现屏幕上出现了这样一串文字(可能有出入):
Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2023, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
大家很可能看不懂这串东西。不过不用慌,我们带大家分析一下上面的几行文字。首先是前两行:
Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2023, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
这是Python解释器开始运行交互模式后打印的一些信息,比如第一行打头的“Python 3.10.7”这个版本号。
接着,Python 说我们可以输入(Type)"help
"、"copyright
"、"credits
"或者"license
" 来获取更多信息。
我们不必在意。大家显示的信息不必和这里展示的信息完全一致,但版本号必须是3.x.y的形式。
接着,Python 显示了 >>>
这个提示符(Prompt)。
这个符号就像是数学题目后的“解:”,它表示 Python 正在等待着你的输入。
我们现在不妨给 Python 出一道数学题,看看 Python 会不会计算小学生都会算的事情。
结果如下:
>>> 1 + 1
2
可以看到,Python 很灵性地计算了 1 + 1
,得到 2
,并把 2
输出给你看,然后很积极地再打出了提示符 >>>
,提醒你输入下一个表达式或语句来让它计算。
概言之,交互模式便是一种“你输入表达式或语句,Python 解释器执行它,Python 把结果告诉你,你再输入表达式或语句,Python解释器再执行它……”的重复过程,
直到你不想再和 Python 解释器说话,敲入 exit()
或者 quit()
后按下回车,交互模式就结束了。
表达式与语句
接下来,我们来正式学习一下表达式和语句的概念,并在 Python 解释器的交互模式中来检查它们的执行结果。
大家在高中阶段一定见过8 * 8
, a + b
, 7 - (x + y)
之类的数学表达式,
而程序表达式也是类似的,它们由数字(亦称为常数)、变量(如a
, x
等,用字母表示)、运算符(如加法、乘法符号等)等组成,
一些表达式还包含了函数,这在课上会讲到(不妨试着用数学里的“函数”概念去理解它)。
我们知道,如果表达式里面每个部分的值都是已知的(例如,a + b
中a
的值为4
、b
的值为5
),那么整个表达式就可以被求值。
让我们来看看Python里面的表达式和求值是什么样的。
1. 基本表达式
基本表达式(Primitive Expressions)只需要一步便可以求得它的值,例如整数、浮点数(Floating point numbers)和布尔(Boolean)值:
>>> 3
3
>>> 12.5
12.5
>>> True
True
浮点数是一种带小数点的数。之所以叫它浮点数,是因为它有着可以浮动的小数点。浮点数的具体细节与数字在计算机中的底层表示有关,大家在《计算机系统基础》这门课上会进行深入学习(和考试),现在了解即可。
变量名同样也是基本表达式,求值的结果为在当前程序的状态下绑定到的值。我们把与变量相关的内容留在赋值语句的部分。
2. 布尔值与布尔表达式
布尔值只有两种值,True
和False
,表示逻辑上的“真”与“假”。
例如,我们知道数字1是不等于2的,所以若在Python交互模式中输入1 == 2
(这个表达式意思是“1等于2”,在 Python 中一般用==
表示“相等”),
则会得到它的值为False
;相反,如果输入1 != 2
(表示”1不等于2“,在Python中一般用!=
表示”不相等“),则会得到值True
。
这种能计算出布尔值的表达式我们称之为布尔表达式(Boolean Expression)。
>>> 1 == 2
False
>>> 2 == 1
False
>>> 1 != 2
True
3. 算术表达式
可以通过对数字和数学运算符的组合产生复杂的算术表达式(Arithmetic Expression)。
Python 中常见的数学运算有+
(加)、-
(减)、*
(乘)、**
(乘方)和以下三种:
- 浮点除法 (
/
):计算第一个数除以第二个数的结果,得到一个浮点数(即使能够整除)。 - 下取整除法 (
//
):计算第一个数除以第二个数的下取整后的结果,得到一个整数。 - 取模 (
%
):计算第一个数除以第二个数的余数(>0), 得到一个整数。
>>> 1 + 2
3
>>> 3 - 2
1
>>> 5 * 6
30
>>> 7 / 4
1.75
>>> 7 // 4
1
>>> 7 % 4
3
>>> 4 ** 3
64
>>> 2 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Python 并没有给出任何结果,而是返回了一个错误(Error)。
这是因为对 0
作除法没有意义,Python 觉得这是一个错误。
像大家平时学过的数学运算一样,算数操作也有优先级,比如乘方运算的优先级大于乘除运算,乘除运算的优先级大于加减运算。 同样地,你也可以通过“加括号”来改变求值优先级。注意,优先级不需要记忆。当你对运算优先级拿不准的时候,就多加括号!
>>> 2 + 6 / 4
3.5
>>> (2 + 6) / 4
2.0
4. 赋值语句
一个赋值语句(Assignment Statement)由一个变量名和一个表达式组成。它会把变量名与该表达式的值绑定。
>>> a = (100 + 50) // 2
注意:在Python中,不能对语句进行求值。
>>> None==(a=1)
File "<stdin>", line 1
None==(a=1)
^
SyntaxError: invalid syntax
你可以输入一个之前定义过的变量名,例如a,Python 解释器会输出其绑定到的值。如果你还记得之前的内容,a
也是一个基本表达式:
>>> a
75
注意这里a
被绑定到75
, 而非(100 + 50) // 2
—— 变量名绑定到值而非表达式上。
5. print
print()
是Python的内置函数,它可以将一个表达式的值输出到终端上(如果还不清楚函数的概念,就单纯记忆一下它的用法吧)。
print(2024)
也是一条语句:
>>> 2024
2024
>>> print(2024)
2024
>>> 2023 + 1
2024
>>> print(2023 + 1)
2024
上面在交互模式下输入2024
和print(2024)
显示的结果看上去相同,但前者是交互模式下自动输出的每一行表达式的值,
后者是print()
在终端中的输出。在非交互模式下(即执行Python代码时,后面会讲),如果你想知道结果,请用print()
。
退出交互
当你完成与Python的交互后,可以进行以下操作来退出交互:
- 输入
exit()
或者quit()
并回车 - 在Windows下,按下
Ctrl+Z
并回车 - 在macOS和Linux下,按下
Ctrl+D
执行Python文件
大家已经知道,Python代码是由表达式和语句组成的。而现在大家已经学会了一些简单的表达式和语句,并在交互模式下体验了执行它们的效果。 现在,我们就可以把之前学过的表达式和语句都写入到文件中,然后让Python解释器直接从文件中读取它们并执行。
关于编写代码,我们已经事先准备了一个编写好的文件hello.py
(约定俗成,大家以.py
的后缀来表示这是一个Python代码文件)。
这个文件中有下面内容:
2024
print(2024)
在 VSCode 中执行(推荐)
VSCode 终端派
然后就可以在 VSCode 的终端里看到程序的结果:
2024
VSCode 鼠标派
如果你的VSCode安装了python插件,那你就可以在右上角看到一个三角形的图标。
然后就可以在 VSCode 的终端里看到程序的结果:
2024
脚本模式
这种一次性执行文件中的全部代码,然后结束运行的 Python 工作模式,就是脚本模式。
你也可以利用任何任何文本编辑器修改hello.py
,尝试前面提到的其它表达式和语句,或者试一试其它你感兴趣的内容。
程序里明明有两个 2024
,为什么终端里只显示了一个 2024
?
可以回顾一下前面的内容。
你会发现,原来2024
这种表达式仅会在交互模式下显示求值结果。这里不是交互模式,所以表达式2024
没有显示任何结果。
屏幕上的2024
是由print函数“打印”出来的(难怪它叫print
)!
小结
到现在,同学们应该已经完成了以下内容:
- 安装好了 Python 环境
- 安装好了 VSCode 编辑器
- 掌握了使用 VSCode 来:
- 打开文件或目录
- 安装插件
- 在终端中执行简单的命令
- 使用 VSCode 终端或 IDLE 来:
- 在交互模式中利用 Python 计算表达式、执行赋值语句
- 在脚本模式中利用 Python 执行文件中的代码
如何完成实验
现在,大家应该熟悉了如何用编写Python代码以及运行代码的过程。那接下来,我们就要讲解大家可能最不愿意看到的东西了——如何完成实验?
在我们的课程实验中,大家需要补充作业文件中的函数定义来完成一些题目(暂时不知道函数是什么也没关系)。不要担心,我们首先带大家一起完成一道题目来熟悉这个流程。
作业文件
对于本次实验而言是 lab00-Code.zip
这个压缩包。
解压
下载到电脑里的实验文件需要解压后使用。我们推荐为每个实验建立一个单独的文件夹以方便后续的管理。 Windows 下的解压可以参照这篇文章的内容。
用 VSCode 打开
打开刚刚解压到的文件夹,右键点击一处空白区域,再选择【用Code打开】。这样操作相当于用VSCode打开整个文件夹,这对后面的实验会比较方便。
Hello, Code!
本次实验的作业文件是lab00.py
。
打开lab00.py
,首先能看到下面这段代码:
def twenty_twenty_four():
"""Come up with the most creative expression that evaluates to 2024,
using only numbers and the +, *, and - operators.
>>> twenty_twenty_four()
2024
"""
return ______
上面代码里包含了我们将要完成的第一个题目——2024(这个题目就叫做2024)。看到上面的代码时,你可能是一脸懵逼的。不过不要慌,我们来一步一步理解。
首先,这段代码使用def twenty_twenty_four()
定义了一个函数twenty_twenty_four()
。
接下来,我们会看到一段被三引号 """
包围的文本,我们把这段文本叫做 docstring(全称 document string,即文档字符串),它一般被用来描述一个函数应该做什么。对于 python 来说,这段文本就是一段注释(Comment)而已,并不会被执行。
在 twenty_twenty_four
的例子中,这段 docstring 告诉我们:“写出一个富有创造力的表达式,使得计算它的值能得到 2024”(当然,在本次实验中没那么有创造力也无妨)。此外,它还要求我们“只可以使用数字、加法、乘法和减法”。
紧接着,我们能看到有一行 docstring 是以 >>>
开头的。从这行开始往下的 docstring 被叫做doctest (举一反三一下,它的全称为什么?——document test)。回想一下大家在前面使用的 Python 交互模式,>>>
被我们称为提示符——它告诉我们可以在它后面输入表达式或者语句。是不是当你在 >>>
后面输入一个表达式后,Python 解释器就会在下一行打印这个表达式的值?doctest 就是通过这样的示例来告诉你一个函数会做什么:“当我们在交互模式下这样输入代码,就会得到这样的结果”。
在我们 twenty_twenty_four
的例子中,doctest 告诉我们:“当我们在交互模式下输入 twenty_twenty_four()
,就会得到 2024”。
总的来说,docstring 和 doctest 分别使用“描述”和“举例”的手段来告诉我们一个函数的行为。未来在大家完成实验题目的时候,除了阅读实验讲义,也最好读一读它们加深自己的理解。
写点代码
现在,我们应该理解了作业文件中的第一个题目,即写一个(有创造力的)表达式,使得它的值为2024
。
那么我们该把这个表达式写在哪里呢?我们的实验讲义会把这种要求都描述清楚。本次实验其它题目的描述可以在左侧的“本次实验内容”一节中找到。作为一个带大家一起做的例子,我们现在先告诉大家第一个题目的要求:请用你想到的那个表达式替换掉twenty_twenty_four
中下划线的部分。
当然,我们之前说过第一个题目我们会带大家一起做。所以虽然不知道富有创造力的大家想到了什么表达式,但没有创造力的助教们就想了一个这样的答案:
def twenty_twenty_four():
"""Come up with the most creative expression that evaluates to 2024,
using only numbers and the +, *, and - operators.
>>> twenty_twenty_four()
2024
"""
return 2025 - 1
助教也试着让 ChatGPT 做做这道题目,它给出的答案是这样的:
# Here’s a fun and creative way to generate the number 2024 using just numbers and the `+`, `*`, and `-` operators:
def twenty_twenty_four():
"""Come up with the most creative expression that evaluates to 2024,
using only numbers and the +, *, and - operators.
>>> twenty_twenty_four()
2024
"""
return (2000 * 1) + (2 * 12)
# Explanation:
# - We use `2000 * 1` to generate 2000.
# - Then, we add `(2 * 12)` to generate 24.
# - The sum results in `2024`.
# This adheres to the constraints while using a little bit of creative multiplication and addition!
大家可以修改各自得到的lab00.py
文件,把下划线替换成自己想到的表达式。
我实现的对不对?
现在,大家已经完成了第一个题目。这个题目当然是很简单的,但之后随着知识的增多,我们要完成的题目也越发复杂。那么有没有什么方法能检验我们写的代码对不对呢?
手动测试代码
回想一下,doctest描述了“当我们在交互模式下这样输入代码,就会得到这样的结果”,那么我们是不是可以打开交互模式,按照doctest的描述输入代码,然后观察结果是不是一致的呢?
是的!我们可以打开一个交互模式的 python,然后把作业文件粘贴进去,最后按一下回车,python就把文件的代码运行完了。然后,我们就可以对照着 doctest 输入 doctest 即可测试:
$ python
>>> def twenty_twenty_four():
... """Come up with the most creative expression that evaluates to 2024,
... using only numbers and the +, *, and - operators.
...
... Expected result:
... >>> twenty_twenty_four()
... 2024
... """
... return 2024
...
>>>
>>> def sum(a, b):
... """Compute the sum of a and b.
...
... Expected result:
... >>> sum(1, 2)
... 3
... >>> sum(3, 8)
... 11
... """
... return ______
...
>>>
>>> def diff_square(a, b):
... """Compute the difference of square a and square b.
...
... Expected result:
... >>> diff_square(3, 2)
... 5
... >>> diff_square(3, 4)
... -7
... """
... return ______
...
>>> twenty_twenty_four()
2024
这和doctest描述的一模一样,所以你现在可以松口气,相信自己的代码是对的了。如果发现不一样,你就需要看看自己哪里出问题了。
刚才其实有一个概念的陷阱。现在让我们思考一个问题:为什么说,经过刚才这样一个过程,我们就有理由相信我们的代码是对的呢?
实际上,“相信自己的代码是对的”是一个错误的说法!准确的说,大家只能有理由相信“自己写的代码和doctest所预期的相符合”,进而推论,自己的代码可能是对的。
其实,像这样一个比较代码的实际执行结果和预期结果的过程就叫做测试(Testing)。这也是“doctest”中test的中文含义。
测试不能保证代码是对的,只能保证代码在多大程度上与预期相符合。但如果通过了足够多、足够复杂的测J试,我们就更加有信心,更容易相信我们的代码是对的。 反过来讲,如果代码连基本的测试都没有通过,说明肯定不是对的。
当然有!
- 形式化验证(Formal Verification),通过逻辑和推理规则(而非一个一个的测试样本)来证明程序的正确性。这种技术在涉及生命财产安全的领域有更广泛的运用。
- 静态分析(Static Analysis),通过抽象和近似来确保程序的安全性。这种技术在当今流行的 IDE、代码编辑器和编译器中都有广泛的运用。
使用Ok进行自动化测试
随着大家做的题目越来越多、越来越复杂,同学们可能会发现,每当自己写了一段新的代码,就要重新走上面一套doctest的流程,这样会非常麻烦。
程序员都是懒惰的生物,嫌麻烦是人类的天性,这也是为什么我们要开发各种各样的减轻负担的工具。 为了减轻同学们测试的负担,tygg 开发了 SICP 插件。在前面的章节中我们也介绍了插件的安装。
为什么我点击
Run
(或者Debug
)打开了浏览器,访问了南京大学代码托管系统?
Ok不仅是一个测试工具,我们还需要用它提交作业。 提交作业时,我们通过南京大学代码托管系统的登陆信息来辨别各个人。 在下一节中我们会介绍怎么注册系统并提交作业,现在不需要管。
助教会如何测试我的代码?
对于助教来说,很难一一审查每个人的代码来验证大家写得正不正确,所以我们会采用测试的方法来给大家评分。像我们发布给同学的doctest一般都写得比较简单,所以那些写得不正确的代码也有可能通过doctest,比如像下面这段代码,本来助教希望大家写return a + b
,但有个小机灵鬼直接返回3
试图蒙混过关:
def add(a, b):
"""return the sum of a and b
>>> add(1, 2)
3
"""
return 3
但如果我们的测试写得越“详细”,写得不正确的代码就越难蒙混过关。 同学们可以试试下面这段代码能不能通过 doctest。
def add(a, b):
"""return the sum of a and b
>>> add(1, 2)
3
>>> add(3, 4)
7
"""
return 3
因此,助教们设置了一个在线测评(Online Judge, OJ)网站,用更加“详细”的、对大家隐藏起来的测试来给大家评分,尽可能防止不正确的代码蒙混过关,维护认真做实验的同学的权利。我们会在下一节中教大家如何提交作业,让助教测试你的代码。
提交作业与评分
为了获取分数,我们需要使用随作业下发的 Ok 客户端进行测试并提交。 这里助教特别强调,所有作业都必须使用 Ok 进行测试并提交,作业系统不再提供直接上传文件的提交功能。
使用Ok(VSCode的SICP插件)是保护作业文件、遵守学术诚信的有效手段。 UC Berkeley开发的Ok是一个对作业进行测试、备份、提交的程序。 助教在2021年的时候把Ok进行了魔改,支持了我们自己的作业系统。 张天昀助教在2022年开发了VSCode插件,使得使用Ok不再需要使用终端。 以上所有工具目前都由我们(现任助教)维护,如果发现问题/有建议欢迎向我们提出。
Ok会把你的程序备份到作业系统。 如果你一不小心手滑把电脑砸了/掰成两半了/泡水里了1/格式化了,可以向助教索要最新的备份文件,当然最好的方法就是自己保存好代码。 同时,大家完成作业产生的一系列备份文件,可以用于调查是否存在违反学术诚信的行为,也可以帮助自己证明自己是独立完成作业的。 从2021年至今收集到的数据来看,很多同学完成困难的作业题需要尝试十几次才能获得满分。
我们考虑这样的一个例子:同学A的在布置作业后一直没有任何尝试,在截止日期当晚直接满分,助教都认为困难的题目没有任何尝试就做对了,且复杂的代码与同学B的高度相似。 在没有能够证明A自己独立完成作业的可信证据的情况下,其行为将被认定为抄袭。 作业抄袭或其他学术不端行为初犯将取消作业成绩(两人的成绩都为0),再犯则课程成绩按0分计并提交到本科生院。
1:真实的事件,某位2019级拔尖班学长喝饮料的时候把饮料浇到了电脑上。
系统注册
- 在南京大学电子邮件系统开通自己的学生邮箱,如何开通请看【IT服务】南大邮箱系列问答。
- 在南京大学代码托管系统使用自己的学生邮箱注册一个账号。
- 第一次登录本课程作业系统必须使用学号邮箱,不能是自定义的邮箱地址。
- 如果你已经用其他邮箱注册过代码托管系统,可以在用户设置页面修改自己的邮箱。
- 点击填写SICP2024作业系统注册&小调查,填写完成后过几秒钟就可以登录作业系统了。
提交作业
打开实验中的代码文件,在VSCode中,按下Ctrl+Shift+P
(在macOS里是Command+Shift+P
)打开Command Palette
(中文应该是命令面板
),然后输入submit来进行搜索,点击Submit your code
即可提交。
如果你不习惯使用键盘快捷键,也可以点击VSCode左下角的齿轮,然后点击命令面板
,即可打开命令面板。
运行Submit your code
后,VSCode底部会弹出终端。如果提交成功,Ok会打印提交ID,并展示你提交的得分。
=====================================================================
Assignment: lab00 Getting Started
OK, version XXXX.X.XX
=====================================================================
Submitting lab00 by TA 助教A.
Online Judge received submission ID XXXXXXXX
Open in browser: https://sicp.pascal-lab.net/oj
Waiting for online judge to grade, press Ctrl+C to exit
... 5s
---------------------------------------------------------------------
Submission graded: score = 33% (100 / 300)
-> P1: 100
-> P2: 0
-> P3: 0
Updating OK: XXXX.XX.XX
Updated to version: XXXX.XX.XX
查看评分
打开课程主页,点击右上角的“Online Judge”进入作业系统并登录。
在左边选择“作业列表”,右边就会展示你已经提交的所有作业的得分(多次提交只计算最高得分),该OJ目前只能用来查看分数和截止时间。(其他功能正在开发中qwq)
常见问题
Q: 作业可以提交多少次?
A: 未经特殊说明的题目,在截止时间之前(以服务器时间为准)你可以提交任意多的次数。 提交的次数不会影响你的成绩。
Q: 我把电脑借给别人了。别人不小心用我的账号提交了他的代码怎么办?
A: 请马上联系助教说明情况,越早越好,避免误会。
Q: 我来不及完成作业了,怎么办?
A: 原则上不允许作业迟交,没有提交的作业均为0分。如果遇到特殊/紧急情况,请马上与助教联系,视情况给予补交机会。
实验提交的原理(可选)
下载插件,点点鼠标运行,敲敲键盘写作业,再点点鼠标提交,编程真的 so easy! 你有没有想过这一切背后的原理是什么?
本章为可选内容。
在进行后面的内容之前,请准备好终端。 未经特殊说明的操作在 Windows/macOS/Linux 效果都是类似的。
打开终端
对于使用 Windows 的同学,请在开始菜单中搜索 "powershell" 并打开。
一般来说,终端里运行的是一个特殊的 shell 程序。 如果你用的是 Windows,那么会是 powershell;如果你用的是 macOS,那么就是 zsh;如果你用的是 Linux,那么大概率会是 bash。
终端漫游
打开终端后,shell 会展示一个工作目录(Working Directory),它表明你现在正处在哪个文件夹下。
每个人在刚进入终端时的工作目录可能都有所不同,Windows 可能会在最前面加上文件夹所在的盘符(就是大家熟知的C盘、D盘等等)。 为了方便起见,后面我们会省略与讲解内容无关的文件夹前缀和盘符(或者用户名,如果你用的是 macOS 或者 Linux)。
以助教的 shell 为例,助教此时正处在 homework
文件夹中:
~/homework$
为了查看 homework
文件夹里都有什么东西,你可以输入命令 ls
1 后按回车:
~/homework$ ls
lab00-Code lab01-Code
~/homework$
如果一切顺利,你将会看到工作目录下的所有文件。
例如助教就在 homework
目录里放置了下载并解压完毕的 lab00-Code
文件夹和 lab01-Code
文件夹。
进入一个文件夹
当然工作目录并不总是处在我们希望的位置,能不能像串门一样换一个工作目录呢?
答案是可以的。
在终端输入 cd lab00-Code
2 后回车,就会发现工作目录现在变成了 lab00-Code
这个文件夹。再执行 ls
指令就会发现我们下载好的文件都在里面了。
~/homework$ cd lab0-Code
~/homework/lab00-Code$ ls
hello.py lab00.ok lab00.py ok
输出的格式不一定完全相同,但应当至少包含
ok
、lab00.py
、hello.py
和lab00.ok
四个文件。
同名的文件夹可能有很多(例如程序设计课也可能会有 lab00-Code
),为了表示 homework
下面的这个 lab00-Code
,我们会用 homework/lab00-Code
这样以 /
连接的文件路径(File Path)称呼它,以区别于其他文件夹下的 lab00-Code
。
退出当前文件夹
除了进入一个文件夹,还可以退出一个文件夹。
例如现在我们想要退出 lab00-Code
文件夹,回到 homework
文件夹,然后去看看别的作业。那么我们可以输入 cd ..
~/homework/lab00-Code$ cd ..
~/homework$ ls
lab00-Code lab01-Code
可以看到,我们 ls
的结果又变成了一开始的样子,这说明现在的工作目录又回到了 homework
这个文件夹。
切换盘符(Windows)
以从 C 盘切换到 D 盘为例。在终端中输入 D:
后回车:
PS C:\> D:
PS D:\>
提交作业
现在,请你利用刚刚掌握的命令,把工作目录切换到
lab00-Code
这个解压后的实验文件夹下。
前面提到我们用 ok 来提交实验。
在使用 ls
命令时,你应该看到了两个和 ok 相关的文件,分别是 ok
和 lab00.ok
。
lab00.ok
你可以用编辑器打开 lab00.ok
看看里面的内容。
lab00.ok
描述了:
- 实验的名字,这里是
Getting Started
- 需要提交程序的名字,这里是
lab00.py
- 测试,这里 ok 给每个实验都安排了 doctest 测试
- 和一些你暂时不知道也没有关系的内容
{
"name": "Getting Started",
"endpoint": "lab00",
"src": [
"lab00.py"
],
"tests": {
"lab00.py:twenty_twenty_four": "doctest",
"lab00.py:sum": "doctest",
"lab00.py:diff_square": "doctest"
},
"protocols": [
"file_contents",
"grading",
"unlock",
"analytics",
"backup"
]
}
lab00.ok
是指导 ok
怎么本地测试和提交我们的程序的配置文件(Configuration File),它就像助教写给 ok
的任务。
用 ok 提交作业
ok
是负责提交实验的程序。
要本地测试某一道具体的题目(例如twenty_twenty_four
)只需要输入
python ok -q twenty_twenty_four --local
要本地测试所有题目只需要输入
python ok --local
要提交所有题目只需要输入
python ok --submit
这么多用法,助教是怎么知道要这么用的?
这是个很好的问题!
实际上助教也不是生来就知道怎么用 ok
的。
要想知道 ok
的用法,只需要在终端里输入 python ok --help
,精简后的输出内容展示如下:
$ python ok --help
usage: python ok [--help] [options]
...
You can run all tests with
python ok
...
这里 --help
的用途是让 ok
告诉我们怎么让它替我们交作业(或者是做一些别的事情)。
这段文字虽然长,但理解起来并不困难,你可以试着扫一眼看看能不能发现什么有趣的事情。
插件的原理
TBD
ls 的全称是 List
cd 的全称是 Change Directory
本次实验内容
到此,大家应该顺利地做完并提交了第一道题目。希望这段旅程对大家足够顺利。
接下来,我们还准备了两道简单的题目让大家再熟悉一下整个实验流程。 这些题目对你而言应该是小菜一碟。
此次实验的截止时间(Deadline,或者 DDL)设置为实验课过后一周。 更具体一点,此次实验的 DDL 是 2024-09-27 的 23:59:00(UTC/GMT+08:00),大家不用着急。
等等,23:59:00,为什么不设置在一个整点?
- 不设置在 00:00:00 是为了避免“原来DDL是昨天”的杯具
- 不设置在 23:59:59 是为了鼓励大家提早提交,避免拖到最后一刻

Problem 1: 2024 (100pts)
用数字和+
,*
和-
运算符写一个最酷炫的表达式,来替换掉下面twenty_twenty_four
函数中的下划线。
def twenty_twenty_four():
"""Come up with the most creative expression that evaluates to 2024,
using only numbers and the +, *, and - operators.
Expected result:
>>> twenty_twenty_four()
2024
"""
return ______
Problem 2: A + B (100pts)
写一个代表变量 a 和 b 之和的算术表达式,来替换掉下面sum
函数中的下划线。
def sum(a, b):
"""Compute the sum of a and b.
Expected result:
>>> sum(1, 2)
3
>>> sum(3, 8)
11
"""
return ______
Problem 3: Diff of Two Squares (100pts)
写一个代表变量 a 和 b 的平方差 a2−b2 的算术表达式,来替换掉下面diff_square
函数中的下划线。
def diff_square(a, b):
"""Compute the difference of square a and square b.
Expected result:
>>> diff_square(3, 2)
5
>>> diff_square(3, 4)
-7
"""
return ______
Q&A
这是什么?
助教把一些可能的问题列在这里并提前回答。
关于实验讲义
为什么实验讲义这么长,为什么这么多莫名其妙的术语和链接,为什么我看不懂呜呜呜呜呜呜
lab00 的讲义是助教以“带大家入门”的目的编写的,因此会看起来有些长。 历年的 lab00 讲义有些许差别,你可以比较它们以感受助教们的心思。
术语和链接是带着带“大家入行”的目的放置的。如果遇到了不懂的术语,最好的办法是在搜索引擎(如必应/百度等)中搜索或者询问大语言模型。
如果遇到了讲义中看不懂的内容,请立刻联系助教。 同学们的反馈非常重要!
关于提问
为什么对提问的要求这么高,为什么私戳助教都不理我,为什么要说我的提问看起来像弱智,为什么呜呜呜呜呜呜
不恰当的提问方式只会方便提问的同学。 作为一名(未来的)专业人士,不妨试着先在提问这件小事上专业起来。
我不敢在群聊里提问,我总觉得这让我看起来像个弱智呜呜呜呜呜
请勇敢一些,学习知识并不是什么羞耻的事情。
关于群聊
为什么群里这么多懂哥,为什么群里的讨论我都听不懂,我呜呜呜呜呜呜
正确的提问方式和一些阅读技巧将有助于你筛选群聊中与你关系不大的讨论。 请坐和放宽,学习是个过程。
关于实验手册以外的内容
助教我用的是某宇宙无敌编辑器vim/emacs/warp/...,你这实验手册上没写啊我要怎么完成实验
实验讲义为同学们提供了一个(尽可能)可靠且方便的完成实验的环境,请优先跟着实验讲义操作。