简介:这份Python练习题库及答案文档,面向编程入门者、备考计算机二级或Python基础考试的学生,以及希望系统梳理语法要点的自学者和培训机构学员。题目以填空题形式呈现,覆盖标准库math中的sqrt、None空类型、有序序列、type()与id()函数、列表乘法与切片、列表推导式、zip构造字典、join与split、转义字符、range、print分隔符、sorted与lambda、max/min、元组与字典操作等常见考点,既有概念辨析也有代码输出结果判断。全部内容整理在一个docx文档中,文件大小165KB,共1个文件,无需解压多个附件,打开即可直接对照答案进行练习和复习。目前已有1006人学习下载,说明该题库对基础阶段学习者具有一定的参考价值。通过填空自测的方式,读者可以快速检验自己对Python核心语法的掌握程度,查漏补缺,尤其适合在碎片时间进行针对性巩固。
1. Python 练习题库:一份能自测也能当教案的 90+ 题资源
这份 Python 练习题库是我见过少数把「基础语法覆盖得这么全」的 docx 文档,51 道填空题打底,加上 25 道阅读程序题、10 道程序填空题、16 道编程题,难度从「sqrt 是哪个函数」一直爬升到「哥德巴赫猜想验证」,正好卡在 Python 入门课程的前半段。它不是算法题集,不考数据结构,所有题目都围绕列表、字典、字符串、函数、循环这些日常写脚本最常用的语法展开。适合两类人:一是学完基础语法但不知道怎么检验自己的新手,二是要带学生的讲师——把这 90 多道题拆成三次测验,基本能覆盖半个学期的考点。下面我从题目本身出发,挑高频考点、易错点和可复现的练法逐步拆开讲。
2. 填空题里的高频考点:切片、推导式与内置函数的选择逻辑
2.1 切片与步长的三处边界:负索引、越界与省略
51 道填空题里,跟切片和索引直接相关的至少有 8 道,这是题库的第一大考点。最有代表性的是第 11 题:aList[3:7]对列表[3, 4, 5, 6, 7, 9, 11, 13, 15, 17]切片,结果是[6, 7, 9, 11]。这里有几处新手容易卡住的地方。
第一,切片是左闭右开区间,[3:7]取下标 3、4、5、6 四个元素,不包含下标 7。很多人以为取到 7 个元素,实际取到的是「从第 4 个元素开始连续 4 个」。第二,步长为负数时方向反转,比如第 18 题list(range(6))[::2]得到[0, 2, 4],::2表示从头到尾每两个取一个;如果把::2改成[::-1]就是整个列表倒序,这是回文判断题的底层工具。第三,负索引从尾部数,第 14 题问「最后一个元素下标」是-1,这个规则同时适用于列表、元组和字符串。
aList = [3, 4, 5, 6, 7, 9, 11, 13, 15, 17] print(aList[3:7]) # [6, 7, 9, 11],左闭右开,不含下标 7 print(aList[-4:-1]) # [13, 15, 17],负索引从尾部数 print(aList[::2]) # [3, 5, 7, 11, 15],步长 2 取值 print(aList[100:200]) # [],越界不报错,返回空列表第 33 题更有意思:x[len(x):] = [1, 2]在列表尾部追加元素,结果x变成[3, 5, 7, 1, 2]。这题考察的是切片赋值的语义——切片右侧可以是任意可迭代对象,len(x):代表从尾部开始的位置,等效于x.extend([1, 2])。参数上要注意:切片赋值的右侧即使只有一个元素,也要写成可迭代形式,直接写= 1会抛TypeError。
2.2 列表推导式与条件过滤:一行代码的三种写法
填空题里推导式出现了三次,分别是第 12 题生成 10 个 5、第 30 题找 100 以内能被 13 整除的数、第 36 题过滤x < 3的元素。这三种形态分别对应推导式的三个组成:表达式、循环、条件过滤。
# 形态一:固定值重复,[表达式 for 变量 in 可迭代对象] lst1 = [5 for i in range(10)] print(lst1) # [5, 5, 5, 5, 5, 5, 5, 5, 5, 5] # 形态二:带 if 的条件过滤,[表达式 for 变量 in 可迭代对象 if 条件] lst2 = [i for i in range(100) if i % 13 == 0] print(lst2) # [0, 13, 26, 39, 52, 65, 78, 91] # 形态三:嵌套循环展开(常见做法是把笛卡尔积拍平) lst3 = [(x, y) for x in range(3) for y in range(3)] print(lst3) # [(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)]形态一和形态二的区别在 if 的位置:if 放在 for 后面是过滤,放在表达式位置是三元表达式。比如[i if i % 2 == 0 else -i for i in range(5)]得到[0, -1, 2, -3, 4],这里 if 在前只是条件表达式的一部分,不是过滤。我一般会让学生把这两种形态对照着写一遍,因为考试喜欢在程序填空题里混着考——先是r.get(i, 0) + 1统计频率,然后又让你补[random.randint(1,20) for i in range(50)]这种生成式,一混淆就翻车。
2.3 内置函数与排序:type、id、zip、sorted 的搭配套路
第 4、5 题考type()和id(),第 13 题考dict(zip(a, b)),第 22、23、25 题考sorted、max、min的key参数。这些题单看都不难,但组合起来能反映一个人对 Python 数据模型的理解程度。
dict(zip(a, b))的关键在于zip把两个列表按位置配对成元组序列,dict()再把它转成字典。如果两个列表长度不一致,zip默认截断到短的那一个,不会报错——这是隐藏的行为,答题时没人问,实际用的时候容易踩。sorted的key参数是另一个重点:第 22 题sorted([111, 2, 33], key=lambda x: -len(str(x)))的结果是[111, 33, 2],因为先把每个元素转成字符串再量长度,按长度降序排。第 25 题max(x, key=len)返回'11',因为字符串比较默认按字典序,'3'最大;但按长度比,'11'最长。
a = ['name', 'age', 'sex'] b = ['Dong', 38, 'Male'] c = dict(zip(a, b)) print(c) # {'name': 'Dong', 'age': 38, 'sex': 'Male'} x = ['11', '2', '3'] print(max(x)) # '3',字典序比较,'3' 最大 print(max(x, key=len)) # '11',长度比较,两个字符最长这里的核心逻辑是:key参数接收一个函数,每个元素先经过这个函数转换,再按转换后的值比较。默认的key是恒等函数,所以直接比较元素本身。这个知识点在后面程序填空题的「高频字符排序」里还会用到——items.sort(key=lambda x: x[1], reverse=True),按出现次数降序排列,同一个套路。
2.4 知识点分布表:先看覆盖再决定怎么刷
我按照题型把这 51 道填空题归了一下类,方便你判断这份题库跟自己当前水平的匹配度。
| 考点模块 | 代表题号 | 核心考点 |
|---|---|---|
| 切片与索引 | 11、14、18、33 | 左闭右开、负索引、步长、越界行为 |
| 列表推导式 | 12、30、36 | 生成、过滤、条件表达式 |
| 内置函数 | 4、5、22、23、25 | type、id、sorted/max/min 的 key 参数 |
| 字典操作 | 13、28、29、48 | zip 构造、update、items 遍历 |
| 字符串方法 | 15、19、41、46 | join、split、replace、find |
| 运算符与表达式 | 6、10、21、31、32 | 列表乘法、in、幂运算、取整 |
| 格式化输出 | 20、39、40 | sep 参数、% 格式化、format 占位 |
| 类型与内存 | 8、9、26、27 | 不可变对象、元组单元素写法 |
这个表不是让你死记,而是刷题前先过一遍:哪一行的考点你闭着眼能答,哪一行需要想三秒。表里的题号都对应原文档,你可以直接按题号定位去练。第 26、27 题那个对比很值得单独说——x = (3,)是元组,x = (3)是整数,差一个逗号,类型完全不同,这是 Python 新手最容易忽略的语法细节之一。
3. 阅读程序题的调试视角:函数、作用域与可变对象的坑
3.1 可变默认参数的累积陷阱:为什么两次调用结果不一样
阅读程序题第 20 题是这份题库里含金量最高的一道:
def demo(newitem, old_list=[]): old_list.append(newitem) return old_list def main(): print(demo('a')) # ['a'] print(demo('b')) # ['a', 'b'] main()第一次调用输出['a'],第二次输出['a', 'b']。原因在于:默认参数old_list=[]在函数定义时只创建一次,之后每次调用如果没传第二个参数,用的都是同一个列表对象。append是原地修改,所以第一次加进去的'a'留在了默认列表里,第二次调用接着往里面放'b'。
这个行为在面试里也经常被拎出来问。解决方法是把默认值改成不可变对象,在函数体里做判断:
def demo(newitem, old_list=None): if old_list is None: old_list = [] old_list.append(newitem) return old_list参数说明:None是不可变对象,每次调用都会走if分支创建新列表,互不干扰。我一般会建议学生记住一个规则——可变对象(列表、字典、集合)永远不要直接当默认参数,除非你刻意要利用累积特性,但那种代码可读性极差。
3.2 函数内赋值不会改外部变量:addInterest 的地址玄学
第 19 题的程序更经典,几乎每个 Python 入门班都会讲:
def addInterest(balance, rate): newBalance = balance * (1 + rate) balance = newBalance def main(): amount = 1000 rate = 0.05 addInterest(amount, rate) print(amount) # 1000输出是1000,不是1050。原因:amount是整数,不可变对象。函数里的balance = newBalance只是把局部变量balance重新指向了一个新对象,外部amount的引用没变。很多学生在这里会懵,因为列表传入函数后append是能改外部的——区别在于append是原地修改对象内容,而=赋值是改变变量指向。
配合第 8 题理解更透彻:x = 3时id(x)是某个值,执行x += 6之后id(x)变了,因为整数不可变,x += 6创建了新整数对象 9,x重新指向它。而x[0] = 3这种操作不会改变列表对象的id,因为列表可变,原地改了内容。判断一个操作会不会影响外部变量,就看它改的是「对象内容」还是「变量指向」。
3.3 for-else 与循环中断:break 和 else 的配对逻辑
题库里有三道题涉及for-else和while-else,这是 Python 独有的语法,其他语言基本没有。第 6 题:
s = 0 for i in range(1, 101): s += i else: print(1) # 输出 1for正常走完(没有被break打断)就会执行else块。第 7 题加了if i == 50时break,else块不执行,输出s累加到 50 的值 1275。第 16 题里continue不影响else执行——只要不break,循环耗尽后else照常跑。
这个特性的实用场景是素数和回文的判断。程序填空题第 4 题质因数分解就用到了:
x = eval(input("请输入小于 1000 的整数:")) k = 2 while x > 1: if x % k == 0: print(k, end=" ") x = x // k if x > 1: print("*", end=" ") else: k += 1while x > 1是循环条件,x % k == 0判断 k 是否是 x 的因子,能整除就打印因子并更新 x,不能整除 k 自增。注意k不需要重置回 2——因为 x 已经除掉了之前的所有小因子,剩下部分不可能再被更小的数整除。这个思路在「找质因数」这类题里比用range(2, x)穷举效率高一个量级。
3.4 返回多个值与解包:sort 函数的两个返回值
第 24 题考的是函数返回多个值时用元组收包:
def sort(number1, number2): if number1 < number2: return number1, number2 else: return number2, number1 n1, n2 = sort(3, 2) print('n1 is', n1) # n1 is 2 print('n2 is', n2) # n2 is 3返回值number2, number1本质是元组(2, 3),赋值语句左侧两个变量解包。这里要注意:返回元组时括号可以省略,但初学者容易把它误解成「返回两个值」——其实 Python 函数永远只返回一个对象,多个值只是打包成了元组。第 22 题的return (avg,) + tuple(g)也是同一个原理,(avg,)是单元素元组,和过滤后的g拼接成新元组返回。
4. 程序填空题的补全套路:随机、排序、图形输出与回文判断
4.1 随机数与频率统计:从生成到排序的完整链路
程序填空题第 1 题和第 2 题是完整的「生成—统计—排序—输出」链路,日常处理日志数据时经常用到这个套路。第 1 题补全三处空:random.randint(1,20)、range(50)、r.get(i, 0) + 1。
import random x = [random.randint(1, 20) for i in range(50)] r = dict() for i in x: r[i] = r.get(i, 0) + 1 for k, v in r.items(): print(k, v)r.get(i, 0)是关键——如果键i不存在,get返回默认值 0,再加 1 就是第一次出现的计数;如果存在,返回当前计数值再自增。这比先判断if i in r再分别处理要简洁得多,也是字典统计的惯用写法。
第 2 题的补全点升级了,要求统计前 20 个高频字符,左对齐输出字符、右对齐输出个数。完整的补全答案是:import string、random.choice(x)、key=lambda x: x[1]、"%-15s%5d"。
import string import random x = string.ascii_letters + string.digits + string.punctuation y = [random.choice(x) for i in range(1000)] d = dict() for ch in y: d[ch] = d.get(ch, 0) + 1 items = list(d.items()) items.sort(key=lambda x: x[1], reverse=True) for i in range(20): word, count = items[i] print("%-15s%5d" % (word, count))这里三个参数要讲清楚。random.choice(x)是每次从字符串 x 里随机取一个字符,注意区别于random.sample(x, 10)——choice允许重复,sample是不放回抽样,如果这里用sample就只能取最多len(x)个,而且字符不重复,语义不对。key=lambda x: x[1]指定按元组的第二个元素(计数)排序,默认是升序,reverse=True改成降序。"%-15s%5d"里的-表示左对齐,15 是宽度,%5d是右对齐宽度 5,这两个格式符组合是输出报表的常用写法。
4.2 图形输出题:空格与 end 参数的配合
程序填空第 3 题和第 6 题都是字符图形。第 3 题让补全金字塔:
n = int(input('输入行数:')) for i in range(1, n + 1): print(' ' * (n - i), end=' ') for j in range(1, 2 * i): print('&', end='') print()补全点是n = int(input(...))和' ' * (n - i)。input返回字符串,必须int()转成整数才能做range运算。空格数量n - i是递减的——第 1 行有n-1个空格,第 n 行有 0 个。end=''让&不换行,最后print()才换行。这里最容易错的地方是把' ' * (n - i)写成' ' * i,那图形就变成左对齐而非居中了。
第 6 题的九九乘法表补全是for j in range(1, i + 1)和'%8s' % (s1), end=''。%8s表示字符串右对齐宽度 8,这是为了让乘积结果对齐成矩阵。如果宽度不够(比如%5s),两位数乘积会挤压在一起,输出就乱了。end=''控制不换行,内层循环结束后print()换行。
4.3 回文判断与哥德巴赫猜想:循环边界怎么设
第 8 题的回文判断用双指针解法:
s = '雾锁山头山锁雾' low = 0 high = len(s) - 1 while low < high: if s[low] != s[high]: print(s, "不是回文") break low += 1 high -= 1 else: print(s, "是回文串")补全点是len(s) - 1、s[low] != s[high]、break、high -= 1。边界条件low < high是关键:奇数长度的回文串,中间字符不用比较,所以low和high相遇时循环结束。如果条件是low <= high,中间字符会和自己比一次,逻辑没错但多做了一步无用功。break的作用是提前终止——只要发现一对不相等就断定不是回文,跳出循环后else不执行,不会输出冲突的结果。
哥德巴赫猜想那题需要补range(3, x // 2, 2)、n1 % i == 0、n2 = x - n1:
import math x = eval(input("输入一个数")) while x < 6 or x % 2 == 1: x = eval(input("输入一个数")) for n1 in range(3, x // 2, 2): m1 = int(math.sqrt(n1) + 1) for i in range(2, m1): if n1 % i == 0: break else: n2 = x - n1 m2 = math.ceil(math.sqrt(n2) + 1) for j in range(2, m2): if n2 % j == 0 and j < n2: break else: print(x, '=', n1, '+', n2)range(3, x // 2, 2)从 3 开始、只取奇数、上限是 x 的一半——因为两个素数相加等于 x,其中一个必然不超过 x/2,偶数不可能是素数所以步长取 2。n1 % i == 0判断 n1 是否为素数,for-else里else在break未触发时执行,表示 n1 确实是素数。n2 = x - n1利用减法的思路,算出第一个素数后直接求第二个数,再验一次素数。
5. 避坑与常见问题:这份题库里容易翻车的 6 个细节
5.1 题库自带答案有误:int(4**0.5) 不是 2 以外的任何数
现象:原文档第 21 题「表达式int(4**0.5)的值为 2」,摘要描述里写成了int(40.5)的值为 2,显然是印刷错误——4**0.5是 2 的平方根也就是 2.0,int()取整还是 2;但int(40.5)取整是 40,不是 2。第 16 题阅读程序的答案还混进了乱码。
原因:题库文档经过多次复制粘贴,部分答案和题干对不上,尤其是公式和特殊字符容易损坏。
解决:拿到文档后先别急着背答案,写个脚本把 51 道填空题的表达式全部跑一遍,跟文档答案做差。我后面会给出验证模板。从那以后我每次给学生发练习题库,都强制先跑一遍答案再发。
5.2 列表删除的索引错乱:为什么从后往前删
现象:编程题第 12 题要求删除列表中的所有奇数,如果正序for i in range(len(lst))边遍历边pop,会跳过元素或者越界报错。原文档给出的提示是「从后往前删」。
原因:pop(i)删除元素后,后面所有元素的下标都往前移了一位。正序遍历时i递增,但剩余元素的下标已经变了,相当于每次跳过一个元素。比如[1, 3, 5],i=0删掉 1,列表变成[3, 5],i=1指向的是 5,3 被跳过了。
解决:倒序遍历不依赖后续下标的变化——删除位置 i 的元素不会影响 i 之前的下标。代码写成for i in range(len(lst) - 1, -1, -1),从最后一个元素往前处理。
5.3 字符串 find 找不到时返回 -1,不是抛出异常
现象:第 43 题'Hello world. I like Python.'.find('python')的结果是 -1,很多新手以为是报错。同样的,第 10 题'[3] in [1, 2, 3, 4]'的结果是False,因为in判断的是元素是否在列表里,[3]作为一个整体列表并不在列表中。
原因:find的语义是「返回子串首次出现的下标,找不到返回 -1」,跟index不同——index找不到会抛ValueError。这算是 Python 字符串方法里两个最容易混淆的兄弟。
解决:需要判断子串是否存在时,优先用in或find() != -1;需要拿到下标且确定存在时用index,但要包在try-except里。in用在字符串上判断的是子串,用在列表上判断的是元素,语义不同,别混。
5.4 切片越界不报错:list(range(20))[100:200] 的结果是空列表
现象:第 49 题x[100:200]对只有 20 个元素的列表切片,结果是[],不报IndexError。很多从 C 或 Java 转过来的新手在这题上第一反应是「肯定越界报错」。
原因:Python 切片的边界处理是「截断」而非「校验」——起始下标超过长度就当成从末尾开始,结束下标超过长度就当成到末尾,起始大于结束就返回空列表。这是刻意的设计,为的是让边界处理更宽容。
解决:不需要改代码,但要知道这个行为和索引访问不同。x[100]会抛IndexError,x[100:200]返回[]——切片是安全的,索引是不安全的。判断列表是否为空用if not lst:,不要用if lst[100:200]:这种隐晦的写法。
5.5 字典 update 的键值覆盖:x.update({'a':3, 'd':4}) 之后 sorted(x.items()) 的值
现象:第 48 题x = {'b':1, 'a':2}执行x.update({'a':3, 'd':4})之后,sorted(x.items())的结果是[('a', 3), ('b', 1), ('d', 4)]。注意'a'的值从 2 变成了 3。
原因:update的语义是「合并」,已有键会被新值覆盖,新键直接添加。sorted(x.items())默认按键排序,所以输出顺序是'a'、'b'、'd'。
解决:更新字典时要确认是否希望覆盖已有键。如果只想添加不存在的键,用setdefault或判断if key not in x。另外sorted(x.items())和sorted(x)不同——后者只返回排序后的键列表。
6. 把题库变成自测工具:做一个答案验证脚本和查漏清单
这份题库最大的价值不在「刷完」而在「能用来定位自己的薄弱点」。我提供一个可复现的练法:写一个 Python 脚本,把填空题里所有能程序化验证的表达式做成断言,跑一遍自动核对答案。脚本按「题目编号、原文档答案、实际输出、是否通过」四列输出结果,一眼就能看到哪几题有出入。
# verify_quiz.py # 用法:python verify_quiz.py,逐题验证填空题的实际输出 import math cases = [ # (题号, 描述, 待执行表达式, 文档给的预期结果) (6, "列表乘法", "[1, 2, 3] * 3", "[1, 2, 3, 1, 2, 3, 1, 2, 3]"), (17, "range 步长", "list(range(1, 10, 3))", "[1, 4, 7]"), (18, "切片步长", "list(range(6))[::2]", "[0, 2, 4]"), (21, "幂与取整", "int(4**0.5)", "2"), (30, "推导式过滤", "[i for i in range(100) if i%13==0]", "[0, 13, 26, 39, 52, 65, 78, 91]"), (34, "zip 转列表", "list(zip([1,2], [3,4]))", "[(1, 3), (2, 4)]"), ] for num, desc, expr, expected in cases: actual = str(eval(expr)) # 把单引号统一成双引号再比较,避免字符串表示差异 ok = (actual == expected) or (actual.replace("'", '"') == expected.replace("'", '"')) status = "PASS" if ok else "FAIL" print(f"[{status}] 第{num}题 ({desc}): 实际输出 {actual} | 文档答案 {expected}")逻辑说明:eval把字符串当表达式执行,str()把结果转成可打印形式。比较时做了单双引号归一化处理,因为repr对字符串列表的输出是单引号,而文档答案里用的是双引号,不处理会误报 FAIL。math模块的导入是为了方便你自行扩展题目时用math.sqrt这类函数。
扩展方法很简单:往cases列表里加元组就行。比如想加第 22 题,就是("sorted 带 key", "sorted([111, 2, 33], key=lambda x: -len(str(x)))", "[111, 33, 2]")。如果表达式里有lambda,要写成一行,eval是支持的。遇到文档答案本身就是错的题(比如摘要里那个int(40.5)),脚本会标 FAIL——这时你反而赚了,因为你发现了一个需要手动确认的边界题。
跑完脚本还会暴露一个信息:哪些题你「看一眼就会」,哪些题你「跑出来才知道答案」。前者是舒适区,后者才是要补的地方。我一般建议用下面这个表做查漏,每过一周自测一次,在「是否熟练」列打勾或打叉:
| 考点 | 代表题 | 判断标准 | 是否熟练 |
|---|---|---|---|
| 切片边界 | 11、33 | 能不看答案写出越界、负索引、步长的结果 | |
| 推导式 | 12、30、36 | 能在一分钟内写出三种形态的推导式 | |
| 字典统计 | 程序填空 1 | 能默写d.get(key, 0) + 1模式 | |
| 函数默认参数 | 阅读 20 | 能解释两次调用为何输出不同 | |
| for-else | 阅读 6、7 | 能说清 break 与 else 的关系 | |
| 字符串格式 | 程序填空 2 | 能写出%-15s%5d并解释对齐方向 |
如果某一行连续两次都是叉,别急着往下刷,回到对应章节重看一遍原理再动手。这份题库存着以后复习也有用——基础语法这东西,两个月不碰真的会生疏。我从那次发现文档答案有错之后,每次拿新的练习资料都先花十分钟把验证脚本跑一遍再讲给学生,省掉了不少课堂上对答案的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取