Term Test 1 Review

CSC108H5: Introduction to Computer Programming

知识点复习 · 卷子补充 · 2024 TT1 真题逐题讲

怎么用这套课件

按键作用
→ / 空格下一步。题目页会先揭开答案,再翻页
←上一步
Esc总览所有页,点一页跳过去
N打开 / 关闭讲解备注(给讲课的人看)
M打开 / 收起目录
F全屏
蓝底:规则
黄底:题目,先做再按 →
这一页最该带走的一句

标题后面有 卷子补充 的页,是原复习 PPT 没讲、但 2024 TT1 考到的内容。

今天的路线

List 和循环在附录仓库:List 与循环

PART 1 / 10

运算符

//   **   %

// 整除:往下取整

a // b = 不超过 a / b 的最大整数(floor)
表达式a / b往下取整结果
4 // 22.0本来就是整数2
3 // 21.51.5 → 11
2 // 30.666…0.666… → 00
7.0 // 23.53.5 → 3,有 float 所以是 3.03.0
-4 // 3-1.333…-1.333… → -2-2
不是四舍五入。3 // 2 是 1,不是 2。

负数:在数轴上往左走

-3 -2 -1 0 1 -4 / 3 = -1.333…

-4 // 3 是 -2:从 -1.333… 往左找最近的整数。

和 int() 对比 卷子补充

规则7 / 2 = 3.5-4 / 3 = -1.33…
a // b往负无穷取整3-2
int(a / b)砍掉小数,往 0 靠3-1

卷子后面附的 API sheet 写着:int(x) 对 float 是 “truncated towards zero”。正数两者一样,负数不一样。

** 乘方

a ** b 是 a 的 b 次方。Python 里 ^ 不是乘方。

表达式结果说明
2 ** 38
2.5 ** 26.25有 float,结果是 float
2 ** -10.5负指数得到 float
(-1) ** 21括号不能丢
2 ** 01任何数的 0 次方是 1

% 取余数

5 ÷ 3 = 1 … 2

5 // 3 得到商 1,5 % 3 得到余数 2。

表达式结果怎么想
3 % 213 = 2 × 1 + 1
4 % 20整除,余 0
2 % 42不够除:商 0,余 2
-5 % 21下一页用公式算
除数是正数时,a % b 一定在 0 到 b - 1 之间。常见用法:n % 2 == 0 判断偶数,n % 10 取个位。

一个公式管住 // 和 %

a % b = a - b * (a // b)

算 -5 % 2

① 先算商:-5 / 2 = -2.5,往下取整 → -5 // 2 = -3

② 代入:-5 % 2 = -5 - 2 * (-3) = -5 + 6

③ 结果:1

验算:2 * (-3) + 1 = -5 ✓

负数的 % 不要凭感觉写 -1。先算 //,再代公式。

常考:取出某一位数字

3万1千4百8十7个
取第 k 位:(n // 这一位的位值) % 10。先用 // 去掉右边的位,再用 % 10 留下最后一位。
要哪一位写法n = 31487
个位n % 107
十位(n // 10) % 108
百位(n // 100) % 104
百位(另一种)(n % 1000) // 1004

2024 TT1 第 1 题就是这个:三位数取中间那一位。

先算谁

优先级运算符
最高()
**
* / // %
+ -
< <= > >= == != in
not
and
最低or
同一级从左往右算。

/ 永远得到 float:4 / 2 是 2.0。

看不清就自己加括号,考试也可以这样拆着算。

练一练 · 运算符

>>> -5 % 3
>>> 5 / 2 ** 3 * 8 - 6
3:00

-5 // 3 = -2(-1.67 往下)

-5 - 3 * (-2) = -5 + 6 = 1

5 / (2 ** 3) * 8 - 6

= 5 / 8 * 8 - 6

= 5.0 - 6 = -1.0

PART 2 / 10

and 和 or

混在一行里时,先看谁

规则只有两条

aba and ba or b
TrueTrueTrueTrue
TrueFalseFalseTrue
FalseTrueFalseTrue
FalseFalseFalseFalse

and:全 True 才 True,有一个 False 就 False

or:有一个 True 就 True,全 False 才 False

没有括号时:not 先,and 其次,or 最后。
看到连续的 and,先给它们打上括号。

拆开看

True or True and False

= True or (True and False)给 and 打括号

= True or False

= True

False and True or False and True

= (False and True) or (False and True)

= False or False

= False

了解:and / or 返回的是操作数

x and y:x 为假就返回 x,否则返回 y
x or y:x 为真就返回 x,否则返回 y
>>> 0 and 1
>>> 1 and 2
>>> 0 or 5
>>> "" or "hi"
0
2
5
'hi'

0、""、[]、None 当条件用时算假,其余大多算真。

练一练 · and / or

① True or False and False or False and True
② True or False and True and True and False or True
2:00

① True or (False and False) or (False and True) → True or False or False → True

② True or (False and True and True and False) or True → True or False or True → True

小技巧:只要 or 连起来的某一段是 True,整个式子就是 True。

PART 3 / 10

条件语句

if   elif   else

if / elif / else

if <condition>:
    ...
elif <condition>:
    ...
elif <condition>:
    ...
else:
    ...
  • 冒号不能丢,下一行要缩进
  • 必须先有一个 if
  • elif 可以有很多个
  • else 最多一个,放最后,不写条件
从上往下检查,第一个为 True 的分支执行,其余全部跳过。

走一遍:x = 75

x = 75
if x >= 80:
    print("A")
elif x >= 70:
    print("B")
elif x >= 60:
    print("C")
else:
    print("F")

75 >= 80?False,跳过

75 >= 70?True

打印 B

后面的 x >= 60 也是 True,但不会再检查

分数等级:两种顺序都对

80+ A,70–79 B,60–69 C,50–59 D,不到 50 是 F。x 在 0 到 100。

从高往低

if x >= 80:
    print("A")
elif x >= 70:
    print("B")
elif x >= 60:
    print("C")
elif x >= 50:
    print("D")
else:
    print("F")

从低往高

if x < 50:
    print("F")
elif x < 60:
    print("D")
elif x < 70:
    print("C")
elif x < 80:
    print("B")
else:
    print("A")

为什么 B 不用写 x >= 70 and x < 80?
因为能走到这一行,就说明前面的 x >= 80 已经是 False,也就是 x < 80 已经确定了。所以这里只需要再判断 x >= 70。

顺序写反就错

x = 90
if x >= 50:
    print("D")
elif x >= 80:
    print("A")   # 永远到不了
D
条件是“往下越来越宽”的时候,窄的要放前面。90 先撞上 x >= 50,就停在 D 了。

如果 x 可能不在 0–100,就把区间写全,比如 elif 70 <= x < 80:。

if…elif 和两个独立的 if 不一样 卷子补充

def f(x):
    t = 0
    if x > 10:
        t = t + 10
    elif x > 5:
        t = t + 5
    return t
def g(x):
    t = 0
    if x > 10:
        t = t + 10
    if x > 5:
        t = t + 5
    return t
xf(x):只走一个分支g(x):两个 if 各自检查
121015
855
300

2024 TT1 第 9 题的 func_b 就是两个独立的 if。

嵌套 if:else 里面再判断 卷子补充

if age < 12:
    price = 5
else:
    if is_student:
        price = 8
    else:
        price = 12
if age < 12:
    price = 5
elif is_student:
    price = 8
else:
    price = 12

左右两段完全等价:else: 里只有一个 if 时,就等于 elif。

ageis_studentprice
10True5(内层根本不看)
20True8
20False12

2024 TT1 第 2 题:用表格反推嵌套 if 的两个空。

PART 4 / 10

函数与 Scope

函数里面的名字,和函数外面的名字

一个函数由哪几块组成

def triangle_area(base: float, height: float) -> float:
    return base * height / 2

area = triangle_area(3, 4)
部分这里是作用
函数名triangle_area调用时用的名字
参数 parameterbase, height函数里的 local 变量,调用时才有值
类型标注: float 和 -> float说明输入、输出应该是什么类型(type contract)
returnbase * height / 2把结果交回去,并立刻结束函数
实参 argument3, 4按位置交给参数:base = 3,height = 4

return 和 print 不是一回事 卷子补充

def f(x):
    print(x * 2)

def g(x):
    return x * 2
>>> a = f(3)
6
>>> b = g(3)
>>> print(a, b)
None 6
print 只是把东西显示在屏幕上,函数的返回值还是 None。
没写 return 的函数,返回值就是 None。
想让别人拿到结果,用 return。题目问“返回什么”,看 return;问“打印什么”,看 print。

函数里建的变量,外面看不到

>>> def f():
...     x = 5
>>> f()
>>> x
NameError: name 'x' is not defined

调用 f() 时:

global
f函数
f
x5

调用结束后,f 的这个框就没了:

global
f函数
f(已结束)
x5
local:在函数里赋值的名字,只活在这次调用里。global:在所有函数外面赋值的名字。

里面再写一次同名变量,外面不会变

>>> x = 4
>>> def f():
...     x = 5
>>> f()
>>> x
4
global
x4
f函数
f
x5

两个 x 住在不同的框里。x = 5 是在 f 的框里新建一个 x,不是改外面那个。

真要改外面的值:global

>>> x = 4
>>> def f():
...     global x
...     x = 5
>>> f()
>>> x
5
global
x4 5
f函数
f
(没有自己的 x)
global x 写在函数里、用 x 之前。意思是:这个函数里的 x 就是外面那个 x。

读变量:先找自己框里的

>>> x = 4
>>> def f():
...     return x
>>> f()
>>> x
4
4

f 里没有给 x 赋值 → 去 global 找,读到 4

>>> x = 4
>>> def f():
...     x = 5
...     return x
>>> f()
>>> x
5
4

f 里给 x 赋过值 → 只用自己的 x

函数里只要对某个名字赋值,这个名字在整个函数里都是 local。没赋值,才去读 global。

UnboundLocalError

>>> y = 4
>>> def b():
...     y += 2
...     return y
>>> b()
UnboundLocalError

① y += 2 就是 y = y + 2,这是赋值

② 有赋值 → y 在 b 里是 local

③ 算右边 y + 2 时,local y 还没有值 → 报错

修法:在函数第一行写 global y。或者别改 y,改成 return y + 2。

参数也是 local 变量 卷子补充

x = 10
def add_one(x):
    x = x + 1
    return x

>>> add_one(3)
4
>>> x
10
global
x10
add_one
x3 4

调用 add_one(3) 时,Python 先在新框里做一次 x = 3。

函数里怎么改参数,外面的 x 都还是 10。

实参按位置交给参数,名字无关 卷子补充

def f(y, x):
    return y - x

>>> x = 1
>>> y = 5
>>> f(x, y)

调用 f(x, y) 就是 f(1, 5)

第一个位置给 y:y = 1

第二个位置给 x:x = 5

返回 1 - 5 = -4

不要看到外面叫 x 就以为对上里面的 x。只看第几个位置。

2024 TT1 第 8 题 well(y, x)、hello(z, x) 就是专门考这个。

练一练 · scope

下面 a() 返回什么?
def a():
    y = 3 * x * z
    return y * b()

def b():
    z = 5
    return 4 * x * z

x = 3
z = 4
a()
5:00

global:x = 3,z = 4

a 里没给 x、z 赋值 → 用 global:y = 3 * 3 * 4 = 36

b 里 z 是 local 5,x 还是 global 3:4 * 3 * 5 = 60

a() 返回 36 * 60 = 2160

PART 5 / 10

字符串

下标 · slicing · 拼接 · 比较 · 方法

交互终端的显示 ≠ print 的输出

输入两种写法,字符串的内容相同

>>> 'cat'
>>> "cat"
'cat'
'cat'

print 只把内容印出来

>>> print('cat')
>>> print("cat")
cat
cat
交互终端自动显示的是字符串的 repr(表示形式);外层引号提示“这是字符串”,不是字符串内容。print 显示内容。

引号嵌套:看清哪些是内容

>>> "It's OK"
>>> 'It\'s OK'
"It's OK"
"It's OK"
>>> print("It's OK")
>>> print('It\'s OK')
>>> print('"Hi"')
It's OK
It's OK
"Hi"
双引号字符串里可直接写单引号;单引号字符串里要写单引号,用反斜杠 \' 转义,或改用双引号包住。
repr 会选合适的外层引号:"It's OK" 在交互终端显示双引号。所以“终端永远显示单引号”不成立。

下标 index

蓝色是从左数(从 0 开始),橙色是从右数(从 -1 开始)。

长度为 n:正下标 0 … n-1,负下标 -1 … -n。
s[0] 是第一个,s[-1] 是最后一个,s[n-1] 也是最后一个。
下标越界会 IndexError:"abc"[3]。slicing 越界不会报错。

练一练 · "Minecraft" 里的 'a'

哪些下标能取到 'a'?   a. 6    b. 7    c. -3    d. -2
答案 a 和 c。s[6] 和 s[-3] 是同一个格子。7 和 -2 都是 'f'。

切片先看“位置”,再看字符下标

s = 'ABCDE':蓝色数字是字符下标;下方 0…5 是字符之间的切割位置。

① s[2:4] 从位置 2 开始,也就是 C 前面。

② 到位置 4 停,在 E 前面切开。

③ 先取下标 2 的 C。

④ 再取下标 3 的 D;下标 4 的 E 不取。结果 'CD'。

终点很大:切到末尾,仍不取不存在的字符

s = 'ABCDE',len(s) = 5;合法字符下标只有 0…4。

起点:2 取 C(下标 2) 取 D(下标 3) 取 E(下标 4) 到达长度 5:停止
s[2:10] → 'CDE'。10 是终点上限,超出长度也不会报错;切片不会生成下标 5…9 的字符。
对照:单个下标 s[10] 会报 IndexError。

slicing:含头不含尾

>>> x = "University of Toronto"
>>> x[14:20]
>>> x[14:21]
>>> x[14:]

'Toront'
'Toronto'
'Toronto'
s[start:stop] 从 start 开始,到 stop 前一个为止。黄色就是 x[14:20],20 号的 'o' 不算。

结果是一个新字符串,原来的 x 不变。

省略起点 / 终点

写法结果说明
s[:3]'Tor'省略 start:从头开始
s[3:]'onto'省略 stop:一直到最后,包括最后一个
s[-4:]'onto'-4 就是 3 号格
s[:]'Toronto'整串复制一份
s[3:100]'onto'越界不报错,切到末尾为止
s[5:2]''起点在终点右边 → 空字符串

练一练 · 切出 Computer 和 Science

z = "Computer Science",写出几种能得到 'Computer' 或 'Science' 的切法。

Computer:z[:8]  z[0:8]  z[:-8]  z[0:-8]  z[-16:-8]  z[-16:8]

Science:z[9:]  z[9:16]  z[-7:]  z[-7:16]

起点终点可以一正一负。先把负数换成格子位置(-8 就是 8 号格的空格),再看范围。

第三个数 step:每次跳几格

x = "0123456789"

x[::2] → '02468':从 0 开始,每次 +2

x[1::2] → '13579':从 1 开始,每次 +2

s[start:stop:step]:先确定范围和方向;从 start 开始,每次加 step,到达或越过 stop 时停止。stop 不取。

先截取范围,再从起点每次前进 3 格

s = 'ABCDEFGHIJK',计算 s[3:10:3]

① 原字符串 · 上方数字是原下标
② 取出 s[3:10]:DEFGHIJ(K 在下标 10,不包含)
当前取到的字符等待取值
按 → 或空格继续;按 ← 回看

step 是负数:从右往左走

x = "0123456789"

x[::-2] → '97531':省略 start 时从最后一个开始,每次 -2

x[-2::-2] → '86420':从 -2(字符 8)开始往左

step 为负时,省略 start 是“最右边”,省略 stop 是“一直到最左边”。

反着切:先确定起点,再一格一格往左

s = 'ABCDE',计算 s[4:0:-1]。负步长时,stop=0 仍然不包含。

取 E(下标 4) 取 D(下标 3) 取 C(下标 2) 取 B(下标 1) 到达 stop=0:A 不取
s[4:0:-1] → 'EDCB'。反向的规则也是“取 start,不取 stop”。

反向切片的一个陷阱:省略 stop ≠ 写 -1

s[::-1]:省略 start,从最后一个 E 出发。

接着取 D。

接着取 C。

接着取 B。

最后取 A → 'EDCBA'。

s[4:-1:-1] 却是 '':显式 -1 指向最后一个字符 E,它是不包含的终点。省略 stop 才会一直走过 A。

什么时候切出空字符串 ''

先检查方向,再决定是否取第一个字符:
step 为正,起点必须在终点左边;step 为负,起点必须在终点右边。起点等于终点也为空。
>>> s = "Mississauga"
>>> s[0:11:-1]
>>> s[::-1]
>>> s[8:2]

''
'aguassissiM'
''

s[0:11:-1]:起点 0 不在向左切片的有效范围内 → '',连第一个字符也不取。

整串倒过来用 s[::-1]。step 不能为 0:s[::0] 会报 ValueError。

+ 拼接,* 重复 卷子补充

>>> "We're" + "Here"
>>> 'abc' * 2
>>> 'ab' * 0
>>> "Total: " + str(10)
>>> "Total: " + 10
"We'reHere"
'abcabc'
''
'Total: 10'
TypeError
str + int 会 TypeError。要先 str(10) 把数字变成字符串。

'abc' * 2 是整串重复 'abcabc',不是每个字母重复 'aabbcc'。2024 TT1 第 7 题用到。

in:有没有这个子串 卷子补充

>>> 'cat' in 'concatenate'
>>> 'o' in 'Welcome'
>>> 'CS' in 'csc108'
>>> '' in 'abc'
True
True
False
True
对字符串,x in s 问的是:x 是不是 s 里连续的一段。大小写要一致。

字符串比较:比的是编码

字符编码(ord)
'0' – '9'48 – 57
'A' – 'Z'65 – 90
'a' – 'z'97 – 122
'9' < 'A' < 'Z' < 'a' < 'z'
数字 < 大写 < 小写

ord('a') 是 97,chr(66) 是 'B'。API sheet 上有这两个函数。

多个字符:从左边一个一个比

像查字典:字母一样就看下一个,第一个不一样的字母决定谁更小。

'car' 和 'cat'

c 一样,a 一样。

第三个 r 比 t 靠前,所以 'car' < 'cat'。

'app' 和 'apple'

前面 app 都一样。

'app' 先结束,短的更小,所以 'app' < 'apple'。

练一练 · 字符串比较

表达式第一个不同的地方结果
'tmu' < 'utsg''t' vs 'u'True
'utsg' > 'utsc''g' vs 'c'True
'utm' > 'utmsu''utm' 先比完,短的更小False
'Zoo' < 'apple''Z'(90) vs 'a'(97)True

连着写 'a' < 'b' < 'c' 时,Python 会拆成两小段,两段都要成立:'a' < 'b' 是 True(a 在 b 前面),'b' < 'c' 也是 True(b 在 c 前面),所以整个结果是 True。
不是先算出 True,再拿这个 True 去和 'c' 比。

字符串方法 ①:找、数

方法例子结果
s.count(sub)'banana'.count('a')3
s.find(sub)'banana'.find('an')1(最左边的起点)
s.find(sub, i)'banana'.find('a', 2)3(从下标 2 开始找)
s.rfind(sub)'banana'.rfind('a')5(最右边的起点)
s.find(sub) 找不到'banana'.find('z')-1
s.index(sub) 找不到'banana'.index('z')ValueError
find 找不到返回 -1,index 找不到报错。

字符串方法 ②:改大小写

原来的字符串不会变。s.lower() 返回一个新的,要留下就写 s = s.lower()。
方法按 → 看它做什么
lower()
'CsC108'.lower()
每个字母变小写,数字不动 → 'csc108'
upper()
'CsC108'.upper()
每个字母变大写,数字不动 → 'CSC108'
capitalize()
'hELLO'.capitalize()
只把第一个字符变大写,后面全部变小写 → 'Hello'

名字要写对:没有 lowercase(),只有 lower()。写错是 AttributeError。

去掉空白、替换

灰色块是空格。这些方法同样不改原来的字符串。

方法按 → 看它做什么
strip()
'  hi  '.strip()
去掉两边的空格,中间的字留下 → 'hi'
lstrip()
'  hi  '.lstrip()
只去掉左边,右边的空格还在 → 'hi  '
rstrip()
'  hi  '.rstrip()
只去掉右边,左边的空格还在 → '  hi'
replace(old, new)
'a-b-c'.replace('-', '+')
每一处 '-' 都换成 '+',不是只换第一次 → 'a+b+c'。new 写成 '' 就是把它们删掉。

判断:是不是某一类字符

空字符串 '' 用这些方法,结果都是 False。也是返回 True / False,不改原来的字符串。

方法按 → 看它做什么
isdigit()
'108'.isdigit()
每个字符都是数字才是 True → True。'10a' 是 False
isalpha()
'csc'.isalpha()
每个字符都是字母才是 True → True。'csc108' 是 False
isalnum()
'csc108'.isalnum()
每个字符是字母或数字才是 True → True。'c-s' 是 False
islower()
'csc108'.islower()
字母都是小写,而且至少有一个字母 → True。数字不管。'CsC' 和 '108' 都是 False
isupper()
'CSC108'.isupper()
字母都是大写,而且至少有一个字母 → True。'Csc' 是 False

PART 6 / 10

函数设计、测试、重构

Function Design Recipe · test cases · refactoring

写函数的顺序(Design Recipe)

def is_even(n: int) -> bool:
    """Return True iff n is even.

    >>> is_even(4)
    True
    >>> is_even(3)
    False
    """
    return n % 2 == 0

名字:is_even。一看就知道是在判断偶数。

type contract:参数 n: int,返回 bool。

docstring:""" 里的一句话,说它做什么。

doctest:>>> 后面是调用,下一行是期望结果。

函数体:照着上面那句话写出来的代码。

测试:跑这些例子。4 应该得到 True,3 应该得到 False。

先想清楚“输入什么、输出什么、做什么”,再写代码。
>>> 和后面的文字之间要有一个空格。>>>is_even(4) 不会被当成测试;要写成 >>> is_even(4)。

docstring 和 doctest

def triangle_area(base: float, height: float) -> float:
    """Return the area of a triangle with base and height.

    >>> triangle_area(2, 3)
    3.0
    >>> triangle_area(4, 5)
    10.0
    """
    return base * height / 2
  • docstring 放在函数体第一行,用 """ 包起来
  • >>> 后面写调用,下一行写期望结果

怎么写 description 卷子补充

def has_digit(s: str) -> bool:
    """Return True iff s contains
    at least one digit."""
    for ch in s:
        if ch.isdigit():
            return True
    return False

用参数名。 函数头里叫 s,答案里就写 s。循环里的 ch 不要写进去。

开头写 Return。 只说交出去什么,不要把代码逐行翻译一遍。

交出去的是 True 或 False,所以写 True。iff 是 if and only if,就是“当且仅当”:后面那句话成立 → True,不成立 → False。

遍历 s,如果 ch 是数字就返回 True,最后返回 False。这是在翻译代码。

返回类型看 return 后面那个值,不是前面数过的变量。

return 后面按 → 看交出去什么类型
return True 后面就是 True → bool
return c == n == 只问相不相等。2 == 2 → True,2 == 3 → False。不是把数字 c 交出去 → bool

怎么挑测试用例 卷子补充

例子:letter(x),0 ≤ x ≤ 100,80 分以上 A,70–79 B …… 50 以下 F

测什么x期望
一般情况(每个分支至少一个)90'A'
边界:刚好等于分界值80'A'
边界:分界值下面一点79'B'
范围最小值0'F'
范围最大值100'A'
每个分支至少测一次;每个分界值测“刚好等于”和“刚好小一点”;范围两端各测一次。

题目说了范围(如 0.0 ≤ score ≤ 4.0),测试值就不能超出范围。2024 TT1 第 12 题。

重构 ①:条件本身就是 True / False 卷子补充

改前

if n % 2 == 0:
    return True
else:
    return False

改后

return n % 2 == 0
if flag == True:
    ...
if flag:
    ...
refactoring:行为不变,代码更简单。

重构 ②:合并分支,删掉到不了的代码

改前

if a > 5:
    return True
if b > 5:
    return True
if a > 5:
    return False
return False

两个分支都返回 True → 用 or 合并

走到第 5 行时 a > 5 一定已经是 False,这个 if 永远不会成立,删掉

return a > 5 or b > 5
改完用几组值检查:原来返回什么,现在还得返回什么。

2024 TT1 第 13 题就是这类。

PART 7 / 10

常见 Error

看最后一行:错误类型 + 原因

常见 Error ①

Error例子原因
NameErrory(从没赋值)这个名字不存在
TypeError"Total: " + 10str 不能和 int 相加
TypeError'a' - 'a'字符串不能相减
IndexError'abc'[3]下标越界
'a' < 'b'
'a' - 'a'
ord('a') - ord('a')

只有比较才不用 ord。'a' < 'b' 直接比谁靠前。

相减不是比较。减号不会自动查编码 → TypeError。

要减编码得自己写 ord:97 − 97 → 0。

a = print(1)
a[0]

None 表示“没有东西”。不是 0,也不是 ""。

屏幕写出 1,交回来的是 None,所以 a 是 None。

None 没有格子。a[0] 就是 None[0] → TypeError。

常见 Error ②

int(2.0)
int('2.0')
int(float('2.0'))

2.0 已经是数字。int 往 0 砍 → 2。

'2.0' 是文字。int 读文字时不认小数点 → ValueError。

先 float('2.0') 变成 2.0,再砍 → 2。

'abc'.find('z')
'abc'.index('z')

'abc' 里没有 z。find 找不到就返回 -1。

同样找不到,index 不返回 -1,直接 ValueError。

3 / 0
3 % 0

除数是 0。/、%、// 都算不了 → ZeroDivisionError。

常见 Error ③

'June'.lowercase()
'June'.lower()

str 没有 lowercase 这个方法 → AttributeError。

要写 lower() → 'june'。

y = 4
def b():
    y += 2

y += 2 就是 y = y + 2,这是赋值。

函数里赋值过,y 全程是 local。算右边时还没值 → UnboundLocalError。

外面的 y = 4 用不上。要改它,函数第一行写 global y。

if x > 0:
print(x)

if 下面必须缩进。print 和 if 对齐,Python 看不到 if 的身体 → IndentationError。

解释为什么报错:先写错误类型,再说哪个操作不合法、应该怎么改。

PART 8 / 10

定义与 API sheet

按课件里的英文定义记

定义 ①

词定义
Floating point浮点数A decimal or fractional number.小数,或带分数部分的数。
Floating-point error浮点误差Repeated decimals are stored as approximations.循环小数存下来的是近似值。
Variable变量A name given to a value.给一个值起的名字。
Assignment赋值= binds a name to a value. It is not mathematical equality.= 把名字绑到一个值上。它不是数学里的等于。
Function函数Code that can be called by name.一段可以用名字调用的代码。
return返回A reserved word. It sends a value back to the caller and exits the function.保留字。把值交回调用的人,并且结束这个函数。

定义 ②

词定义
Docstring文档字符串A short description of a function. Be concise and precise.对函数的简短说明。要短,也要准确。
Scope作用域The places where a variable’s name is valid.一个变量名在哪些地方有效。
Global variable全局变量A variable that can be accessed by all functions (unless a local name hides it).所有函数都能用的变量,除非里面有同名的 local 把它挡住。
String字符串Text inside quotes; an ordered collection of characters.引号里的文字;按顺序排好的一串字符。
Newline / tab换行 / 制表符\n starts a new line; \t is a tab.\n 另起一行;\t 是制表符。
Method方法A function designed for one type, called as value.method(...).给某一种类型用的函数,调用时写成 value.method(...)。

定义 ③

词定义
if statementif 语句A control structure that runs a block when its condition is True.一种控制结构:条件是 True 时,才运行下面那一块。
Factoring分解问题Breaking a complex problem into parts that are easier to understand, program, and maintain.把复杂问题拆成几块,让它更好懂、好写、也好维护。
Refactoring重构Restructuring existing code without changing its external behaviour.调整已有代码的结构,但外面的行为不变。
Control structures:if statements 和 loops。assignment、function、method、expression 都不是。控制结构只有 if 语句和循环。赋值、函数、方法、表达式都不是。

内置函数 ①

调用按 → 看它做什么
print(1) 屏幕写出 1,交回来的是 None。要留下结果,用 return。
len('hi')
len([1, 2, 3])
问有几个 → 2,和 3。字符串数字符,list 数元素。
str(10) 变成文字 → '10'。和字符串相加之前要先变,否则 TypeError。
float('2.5')
float(2)
变成小数 → 2.5,和 2.0。
bool(0)
bool('hi')
0、""、[]、None 是 False。别的大多是 True,所以 'hi' → True。
int('12')
int('2.0')
int(-2.7)
'12' → 12。'2.0' 是文字,不认小数点 → ValueError。-2.7 往 0 砍 → -2,不是 -3。

内置函数 ②

调用按 → 看它做什么
type(3)
type(None)
问类型 → <class 'int'>,<class 'NoneType'>。
max(3, 1, 4)
min('cat', 'car')
最大的 → 4。字符串按字典序,'car' 更小 → 'car'。
ord('a')
chr(97)
字符 → 编码 97。编码 → 字符 'a'。比较不用 ord,相减才要。
input() 读用户打的一行,结果永远是 str。打 12 得到 '12',要数字再套 int。

考试附 API sheet:不用背签名,要会看 卷子补充

返回 None(改原来的东西)

print

返回新字符串(原字符串不变)

lower、upper、capitalize、strip、lstrip、rstrip、replace

返回 bool

x in s、isalpha、isdigit、islower、isupper

返回 int

len、count、find(找不到 -1)、index(找不到报错)、ord

API sheet 上 -> 后面写的就是返回类型。看到 -> None,就别把结果赋值给变量。

PART 9 / 10

可能例题

先做,再按 →

从哪一行开始报错

print(int("1"))
print(float("-.8"))
print(float("2"))
print(int("2.0"))
a. 第二行   b. 第三行   c. 第四行   d. 以上都不对

前三行分别打印 1、-0.8、2.0

答案 c。int("2.0") 是 ValueError:int 读字符串时不认小数点。要写 int(float("2.0"))。

"Mississauga" 切片

>>> a = "Mississauga"
>>> a[::3]
>>> a[0:11:-1]
  1. "Mssg", ""
  2. "ssu", ""
  3. "Mssg", "aguassissiM"
  4. "ssu", "aguassissiM"

答案 A。下标 0、3、6、9 是 M、s、s、g。a[0:11:-1] 从 0 往左走,到不了 11 → ''。

跑完之后 x 和 y 是什么

x = 3
y = 4
def a():
    global x
    x += 2
    return x

def b():
    y += 2
    return y

a()
b()

为什么 x 变成 5? global x 就是“用外面那个 x”。x += 2 等于 x = 3 + 2,5 写回外面。

为什么 y 还是 4,还报错? b 没写 global y。y += 2 是赋值,这个 y 全程是自己的。算 y + 2 时自己还没值,不会去读外面的 4 → UnboundLocalError。

报错发生在改 y 之前,外面的 4 还在。a() 已经把 x 改完。最后 x = 5,y = 4。

哪些是 control structure

  1. loop
  2. if-statement
  3. assignment
  4. function
  5. method
  6. expressions
  7. operation

控制结构只管两件事:哪些代码要跑,以及要跑几次。if 看条件,决定跑不跑这块;loop 决定这块重复几次。所以只有这两项。

为什么赋值不是? x = 3 只是把值存下来。这一行写了就会执行,不会跳过,也不会重复。

为什么函数、方法不是? f()、s.lower() 是去调用一段写好的代码。调用本身不负责挑路。

为什么表达式、运算不是? 1 + 2 是在算出一个结果。算完就结束,不管别的代码跑不跑。

会引发什么 Error

代码Error
'a' - 'a'TypeError
'abc'[3]IndexError
3 / 0ZeroDivisionError
x = 3 然后 yNameError

三道计算,一个公式

题目答案过程
-200 % 174-200 // 17 = -12,-200 - 17 * (-12) = 4
True and False or False and False or True or False and TrueTrueand 先算:False or False or True or False
5 / 8 * 2 ** 3 + 49.05 / 8 * 8 + 4 = 5.0 + 4
用 a、b 填 a = _ % _ + _ // _ * _a = a % b + a // b * b

replace 换掉全部

>>> "abs sabs eeabs".replace("abs", "s")
's ss ees'

三处 abs 都换成 s:abs → s,sabs → ss,eeabs → ees。空格保留。

第二次出现的位置

def index_of_the_second_sub(s: str, sub: str) -> int:
    """Return the index of the second sub in s.
    If s contains fewer than 2 subs, return -1.

    >>> index_of_the_second_sub("abcabc", "abc")
    3
    >>> index_of_the_second_sub("", "a")
    -1
    """
    return s.find(sub, s.find(sub) + len(sub))

内层 s.find(sub):第一次出现的起点,"abcabc" 里是 0

+ len(sub):跳过第一次,从 3 开始找

外层 find 从 3 找到 3;找不到就是 -1

PART 10 / 10

2024 Term Test 1 逐题讲

8 题 · 选择题注意 “Circle ALL” 是多选

考点地图

题考点复习页
Q1取中间一位运算符
Q2嵌套 if 填空条件
Q7拼接 + slicing字符串
Q8参数与 scopescope
题考点复习页
Q9if/elif vs 两个 if条件
Q12设计测试用例函数设计
Q13重构条件语句函数设计
Q15slicing + int()字符串

Q1 · Python as a Calculator 1 pt

a 是一个正的三位数。哪个表达式得到 a 的中间一位?(a = 456 时要得到 5)

  1. (a // 10) % 10
  2. (a % 100) // 10
  3. (a % 10) // 10
  4. More than one of the above.
  5. None of the above.
a = 456
A45 % 10 = 5 ✓
B56 // 10 = 5 ✓
C6 // 10 = 0 ✗

A 和 B 都对,所以答案是 D。

Q2 · Conditionals 1 pt

plan \ months12–67+
silver$90$60$60
gold$90$70$35
if months == 1:
    cost = 90
elif ____(1)____:
    cost = 60
else:
    if ____(2)____:
        cost = 70
    else:
        cost = 35
  1. (1) plan == 'silver'
    (2) months >= 2 and months <= 6
  2. (1) plan == 'silver'
    (2) months >= 6
  3. (1) months >= 2 and months <= 6
    (2) plan == 'gold'
  4. All of the above.
  5. None of the above.

(1) 要覆盖 silver 至少 2 个月的 60 元:填 plan == 'silver'。

进入 else 已排除第 1 个月和 silver;gold 的 2–6 个月收 70 元,所以 (2) 要检查月份在 2 到 6 之间。

B 会把 gold 3 个月算成 35 元、7 个月算成 70 元,都不对。答案 A。

Q7 · Strings 1 pt

s = "We're" + "Here"
print(s[4::2])

s = "We'reHere",共 9 个字符:

  1. eHere
  2. eee
  3. rHr
  1. 'eee
  2. error

从下标 4 开始每次 +2:4、6、8 → e、e、e。答案 B。撇号在下标 2,取不到。

Q8 · Variables 1 pt

x = 5
y = 2
z = True

def well(y, x):
    z = 3
    x = y
    y = x
    return x * y * z

def hello(z, x):
    x = z + x
    z = y + x
    return well(x, z)

调用 hello(0, 1) 之后,global 的 x、y、z 是多少?

  1. x = 5, y = 2, z = True
  2. x = 1, y = 1, z = 3
  3. x = 1, y = 2, z = 3
  4. x = 3, y = 2, z = True
  5. An error occurs.

hello(0, 1):按位置 z = 0,x = 1(都是 local)

x = 0 + 1 = 1

hello 里没有 y → 读 global 的 2:z = 2 + 1 = 3

well(1, 3):按位置 y = 1,x = 3

z = 3,x = 1,y = 1,返回 1 * 1 * 3 = 3

global(没变)
x5
y2
zTrue

没有 global,所有赋值都在各自的框里 → A。不会报错,因为 hello 只是读 y。

Q9 · Code Tracing 4 pts

def func_a(value):
    total = 0
    if value >= 15:
        total = total + 20
    elif value >= 10:
        total = total + 10
    else:
        total = total + 1
    return total
def func_b(value):
    total = 0
    if value > 10:
        total = total + 10
    if value <= 5:
        total = total + 5
    else:
        total = total + 2
    return total
代码输出走了哪些行
print(func_a(5))1两个条件都不满足 → else
print(func_b(8))28 > 10 否;8 <= 5 否 → else +2
print(func_a(15))20第一个条件就成立,后面不看
print(func_b(12))12两个独立的 if:先 +10,再走 else +2

前往模拟网站(大家可以收藏) https://pythontutor.com/cp/csc108h.html#mode=edit

Q12 · Test cases 2 pts

def performance_eval(score: float) -> str:
    """0.0 <= score <= 4.0
    Return 'outstanding' if score is at least 3.5, 'exceeds expectations'
    if score is at least 3.2 and less than 3.5, and 'needs improvement' otherwise.
    """
Test Case DescriptionscoreReturn Value
Score below 3.2(题目给的)2.5'needs improvement'
Boundary: exactly 3.23.2'exceeds expectations'
Between 3.2 and 3.53.4'exceeds expectations'
Boundary: exactly 3.53.5'outstanding'
Maximum score4.0'outstanding'

也可以换成 0.0(最小值)或 3.19(刚好低于 3.2)。不能用 4.5、-1 这种超出范围的值。

Q13 · Refactoring 2 pts

def func(x, y):
    if x > 100 and y == 0:
        return True
    if x > 100 and y != 0:
        return True
    if x > 100:
        return False
    elif x < 0 and y == 10:
        return True
    return False

① y 要么 == 0 要么 != 0 → 两个 if 合起来就是 “x > 100 就 True”

② 能走到第 6 行,x > 100 一定是 False,这个分支永远到不了,删掉

③ 剩下:

x < 0 and y == 10 时 True

def func(x, y):
    return x > 100 or (x < 0 and y == 10)

④ 其余都是 False

括号可以不写(and 本来就先算),写上更清楚。我用 x ∈ [-50, 200)、y ∈ [-5, 20) 全部代进去比过,结果和原函数完全一样。

Q15 · Strings 3 pts

CURRENT_YEAR = 2023

def calculate_age(birth_date: str) -> int:
    """birth_date 的格式是 YYYY-MM-DD,不晚于 CURRENT_YEAR 年底。
    返回这个人在 CURRENT_YEAR 年底的年龄。

    >>> calculate_age('2010-05-15')
    13
    >>> calculate_age('1995-10-20')
    28
    >>> calculate_age('2023-01-01')
    0
    """
return CURRENT_YEAR - int(birth_date[:4])

birth_date[:4] → '2010',int(...) → 2010

问的是“年底”的年龄,月和日不影响:2023 - 2010 = 13

要用常量 CURRENT_YEAR,不要写死 2023

进考场前过一遍

  • // 往负无穷取整,int() 往 0 砍;% 用 a = b * (a // b) + a % b 验算
  • 取某一位:(n // 位值) % 10
  • and 先于 or
  • if/elif 只走一个分支;两个独立 if 各自检查
  • 函数里赋值的名字是 local;参数也是 local;实参按位置对应
  • 没有 return 就返回 None
  • slicing 含头不含尾;step 的正负决定方向
  • 'abc' * 2 是 'abcabc';str + int 报错
  • docstring 写 “Return True iff …”;测试挑边界;重构保持行为不变

祝大家考试顺利

卡住时点目录跳回对应的那一章,用例子再算一遍。

Terry

© Terry1238832 制作