习题 — 第 1 章:Python 基础
一组练习题,按它们所练习的小节分组。请自己动手;凡题目中给出代码,都当作给定数据或起点。每题都附示例输出,让你知道目标是什么。本页不提供解答。
关于函数的说明
少数题目会用 def name(args): … return … 定义一个小函数,迭代器相关的题目还会用带 yield 的生成器函数。yield 的写法在 1.4 讲过;函数这个主题会在 第 2 章 正式登场,但这里用到的基本 def 形式就够了。
1. 对象与类型
练习 1.1 — 预测类型
先预测每个值的 type(),再运行核对。哪个是 set,哪个是 dict?哪个是 tuple?
示例输出
练习 1.2 — 布尔表现得像数字
解释每个结果。True 在什么意义上是一个整数?为什么对一串 True/False 求和能数出 True 的个数?
示例输出
练习 1.3 — 但布尔不只是数字
因为 1 == True == 1.0(且它们哈希相同),预测这个字典最终有几个键、哪个值胜出。然后写一个检查 is_real_bool(x),只对真正的布尔值返回 True。在什么场景下,坚持要一个真正的 bool(而非任何为真的值)才真正重要?(提示:isinstance(x, bool)。)
d = {1: "one", True: "true", 1.0: "float"}
print(d)
print(is_real_bool(1)) # 应为 False
print(is_real_bool(True)) # 应为 True
示例输出
练习 1.4 — 何时是同一个对象?
预测每个比较,再运行。为什么 == 是比较值的正确工具,而 is 问的是另一个问题?(257 那一例与实现相关——一次典型运行会打印 False。)
示例输出
练习 1.5 — 哪种数值类型?
对每个表达式,预测结果是 int、float 还是 complex,再用 type() 核对。
示例输出
练习 1.6 — None 也是一个值
预测输出,然后解释内层和外层的 print 各显示什么,以及 print(...) 返回什么。
示例输出
练习 1.7 — type() vs isinstance()
给定 x = True,比较下面两个检查。哪个为 True,为什么?要问“这是个数字吗?”该用哪个;要问“这正好是个布尔值吗?”又该用哪个?
示例输出
可选:基础之外的类型
下面探索几个我们没有正式讲过的内置类型——在真实代码里遇到时能认出来就好。全部可选。
练习 1.8 — array(可选)
array.array 像列表,但所有元素必须是同一种数值类型。创建一个、索引它,看看加入错误类型会怎样。
import array
a = array.array('i', [1, 2, 3, 4, 5]) # 'i' = 有符号整数
print(a)
print(type(a))
print(a[0], a[-1])
# a.append(6.0) # 取消注释:会怎样?
示例输出
练习 1.9 — bytes 与 bytearray(可选)
bytes 是不可变的原始字节序列;bytearray 是它可变的孪生兄弟。两者都试试,并用一种编码在文本与字节之间往返。
b = b"hi"
print(b, type(b))
ba = bytearray(b"hi")
ba[0] = 72 # 'H' 对应的字节
print(ba)
print("café".encode("utf-8"))
print(b"caf\xc3\xa9".decode("utf-8"))
示例输出
练习 1.10 — 精确算术:Fraction 与 Decimal(可选)
浮点数是近似的。把一个浮点求和与 Fraction、Decimal 给出的精确答案比较。
from fractions import Fraction
from decimal import Decimal
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(Fraction(1, 10) + Fraction(2, 10))
print(Decimal("0.1") + Decimal("0.2"))
示例输出
练习 1.11 — 复数的各部分(可选)
complex 是内置数值类型。取出它的实部、虚部和模。
示例输出
练习 1.12 — frozenset(可选)
frozenset 是不可变的集合——因此与 set 不同,它可哈希,能作字典键或另一个集合的成员。
fs = frozenset([1, 2, 2, 3])
print(fs)
# fs.add(4) # 取消注释:会怎样?
d = {fs: "ok"} # frozenset 可以作键
print(d[frozenset([3, 2, 1])])
示例输出
2. 容器集合
练习 2.1 — 正确使用 set() 与 {}
逐行运行,并总结规则:set(...) 何时成功?{...} 何时建出的是集合而非别的东西?最后,如何用 set() 函数创建 {(1, 2, 3)}——一个元素、一个元组?
示例输出
练习 2.2 — KeyError vs get()
展示索引一个不存在的键会引发 KeyError,而 get() 不会。然后查找 "key3",让它返回默认值 "missing" 而不崩溃。
示例输出
练习 2.4 — 集合运算
用下面两个集合,求并集、交集、在 a 但不在 b 的元素、以及对称差。再判断 {2, 3} 是否为 a 的子集。
示例输出
练习 2.5 — 用两个列表构建字典
把名字与分数配成一个字典(一次调用即可)。然后添加 "Dan"(88)、把 "Bob" 更新为 90、并查找可能不存在的 "Eve",不存在时返回 "unknown"。
示例输出
练习 2.6 — 列表可变,元组不可变
预测哪些编辑成功、哪些报错,并用可变性来解释。原地编辑之后,id(l) 会变吗?
示例输出
练习 2.7 — 字符串处理
从下面这段杂乱的标题,生成 slug "data-science-101"(去空白、转小写、空格变连字符)。再打印它有几个单词,以及原字符串的反转。
示例输出
列表入门小练习
一组“手指操”,用来练熟列表的索引、切片、嵌套,以及一个名称如何指向一个列表——这是 Python 里与 C 语言经典指针练习相对应的部分。
练习 2.8 — 从两端索引
用索引打印:第一个元素、最后一个元素、第二个元素、倒数第二个元素(靠近末尾的可试试负索引)。
示例输出
练习 2.9 — 预测切片
运行前先预测每个切片。
xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(xs[2:5])
print(xs[:3])
print(xs[7:])
print(xs[::2])
print(xs[::-1])
示例输出
练习 2.10 — 原地修改
按顺序执行下列操作,每步后打印 xs:末尾追加 60;在索引 1 处插入 15;pop 掉索引 3 的元素;把切片 xs[0:2] 替换为 [1, 2, 3]。
示例输出
练习 2.11 — 列表的列表(矩阵)
打印第 1 行第 2 列的元素(下标从 0 起);整条中间行;以及中间列(取列需要一个循环或推导式)。
示例输出
练习 2.12 — 两个名称,一个列表(“指针”陷阱)
预测两次打印。为什么第一种情形里改 b 也改了 a,第二种却没有?(回忆:名称指向对象;a[:] 是一份拷贝。)
示例输出
练习 2.13 — 手写,不走捷径
不使用 [::-1]、reversed()、max()、sum(),用循环打印列表的反转、它的最大值、以及总和。
示例输出
3. 控制流程
练习 3.1 — 带小数步长的序列
打印 0.0, 0.5, 1.0, …, 10.0。必须用 range(它只产出整数,所以要适当地缩放)。
示例输出
练习 3.2 — 手动追踪循环
先不运行,写出每个变量在每一步的值,再运行核对。
def naive_by2(lst, by):
n = len(lst) // by
res = []
for i in range(n):
res.append([lst[2 * i], lst[2 * i + 1]])
return res
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(naive_by2(nums, 2))
示例输出
练习 3.3 — 重新分组
把列表分成 [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]——先奇后偶——且不使用迭代器或生成器。(提示:带步长的切片 lst[start::step]。)
示例输出
练习 3.4 — FizzBuzz
对 1 到 30:3 的倍数打印 "fizz",5 的倍数打印 "buzz",两者都是的打印 "fizzbuzz",其余打印数字本身。
示例输出
练习 3.6 — 50 以内的素数
只用循环和 if(不导入任何库),打印 2 到 50 之间的每一个素数。
示例输出
练习 3.7 — 用循环做最小二乘
对下面的数据和模型 \(y = b x\),找出使 \(L(b)=\sum_{i=1}^{5}(y_i - b x_i)^2\) 最小的斜率 \(b\)。用循环在 0.50, 0.51, …, 1.50 上搜索 b,记录目前最优的 b。不要使用 min 或 numpy.argmin。
示例输出
4. 可迭代对象与迭代器
练习 4.1 — 手动迭代
用 iter() 和 next() 一次一个地取出列表中的值,并展示取完最后一个之后会发生什么。
示例输出
练习 4.2 — 迭代器是一次性的
预测两次输出,并解释为什么第二次是空的。
示例输出
练习 4.3 — 平方数生成器,两种写法
产出平方数 1, 4, 9, 16, …:(a) 用带 yield 的生成器函数;(b) 用生成器表达式。表达式那种在 100 处停下,并打印结果列表。
示例输出
练习 4.4 — 无穷斐波那契
写一个无限产出斐波那契数的生成器(\(F_0=0\)、\(F_1=1\)、\(F_n=F_{n-1}+F_{n-2}\)),然后打印前 10 个。
示例输出
练习 4.5 — 带筛选的生成器表达式
写一个生成器表达式,产出那些以辅音开头、以元音(a、e、i、o、u)结尾的单词的长度。对下面的列表,它应产出 6、4、5(对应 "banana"、"kiwi"、"grape")。
words = ["apple", "banana", "orange", "kiwi", "grape",
"elephant", "tiger", "lion", "mouse"]
vowels = "aeiou"
示例输出
练习 4.6 — 用生成器产出完全平方数
写一个生成器,产出不超过 100 的完全平方数(1、4、9……),然后把它们打印在一行里。(完全平方数是某个整数 \(n\) 的 \(n^2\)。)
示例输出
练习 4.7 — 列表 vs 生成器的内存占用
比较同一计算下列表与生成器的内存占用,并解释其差异。(具体字节数因平台而异。)
import sys
print(sys.getsizeof([x * x for x in range(10000)]))
print(sys.getsizeof((x * x for x in range(10000))))
示例输出
练习 4.8 — 两参数的 iter()(可选)
iter(f, sentinel) 会反复调用函数 f,直到它返回 sentinel。用它(而非 while 循环)从 0–9 中抽取随机整数,直到首次抽到 0,并打印此前抽到的数字(0 本身不打印)。
示例输出(每次运行不同)