Python函数编程实战:第8章课后题解析与避坑指南

发布时间:2026/10/15 6:11:32
Python函数编程实战:第8章课后题解析与避坑指南 简介本资源为《Python编程实践》第8章练习题及解答作者陈波、刘慧君面向正在学习Python基础、需要巩固函数与算法思维的编程初学者及高校学生。内容围绕函数编程、递归、循环、字符串处理、数字转换、回文素数等核心主题展开涵盖整数各位数字求和、递归计算f(i)1/22/3…i/(i1)、十进制转十六进制/八进制/二进制、不特定多参数乘积、前100个回文素数打印以及折半查找等典型题目。每道题均配有答案、思路解析与示例代码帮助读者理解取模消位、素数筛选、递归边界等关键技巧。资源为1个PDF文件压缩包约224KB轻量便携适合随时查阅。目前已有1935人学习下载可作为课后练习对照与期末复习的实践指南。1. 从一份课后题答案说起这套第 8 章练习到底能解决什么很多人学 Python 卡在函数这一章不是因为语法看不懂而是因为「看得懂、写不出」。变量作用域、递归、可变参数、进制转换、二分查找这些概念单独拎出来都能说明白一旦让你从零写一个完整函数手就停住了。《Python编程实践》第 8 章练习题及解答这份资源恰好就是冲着这个痛点来的——它把函数编程里最容易翻车的几类题型集中在一起每道题都配了可运行的参考实现和思路说明。这份资源适合三类人正在跟这本书做课后作业的学生、想拿几道经典题练手函数抽象的初学者、以及需要一份「函数章节题型清单」来备课或出题的从业者。它不教你 Python 语法本身而是用 9 道题覆盖了作用域判断、递归求和、数字拆解、进制转换、可变参数、回文素数筛选、折半查找、递归改非递归这些高频考点。下面我按「先看懂题在考什么再动手复现最后避开那些一写就错的坑」的顺序把这份资源拆开讲一遍。2. 函数作用域与递归从 8-1 到 8-4 的执行过程拆解2.1 全局变量和局部变量同名时Python 到底用哪个8-2 那道题是整章里最容易被轻视、又最容易在面试里被追问的。题目给了一段代码函数func内部先给x赋值 77然后用y * 2算出z打印xz最后函数外部又打印x和y。输出结果是xy 79、x 1、y 1。很多人第一眼会算成别的数原因就是没搞清楚 Python 的 LEGB 查找规则。x 1 y 1 def func(): x 77 # 这里创建了一个局部变量 x和全局 x 无关 z y * 2 # func 内没有 y向上找到全局 y1z2 print(xy, x z) # 77 2 79 func() print(x, x) # 全局 x 仍是 1 print(y, y) # 全局 y 仍是 1逻辑说明函数体内对x的赋值让x成为局部变量Python 不会去读全局的x而y在函数内没有被赋值所以走全局查找。参数上没有什么可调的但你要记住一个边界——如果函数里只是想读全局变量、不赋值那它用的就是全局值一旦出现赋值语句这个名字在整个函数体内都按局部处理哪怕赋值发生在使用之后也会报UnboundLocalError。2.2 递归求和的展开过程要能手工写出来8-4 给了一段递归代码计算 1 到 9 的奇数和def func(x): if x 1: return 1 return x func(x - 2) print(func(9)) # 25逻辑说明func(9)展开成9 func(7)一路展开到func(1)返回 1最终9753125。这里的关键参数是递归的「步长」——每次减 2 保证只累加奇数终止条件是x 1。如果你把步长改成 1它就变成求 1 到 9 的所有整数和如果把终止条件写成x 0传入偶数时就会漏掉边界。我一般会建议在草稿纸上把前三四层展开写一遍能写对展开式基本就不会写错终止条件。2.3 把递归改成循环的通用套路8-9 要求把分治法找假硬币的递归程序改成非递归。这类题的核心思路就一句话用显式的循环变量替代函数调用栈。原递归版本每次把硬币数量二分比较左右两堆重量然后决定往左走还是往右走。改成循环后用bottom记录当前比较区间的起点num记录当前区间长度每轮更新这两个值即可。def findFalseCoin(coins, start, n): bottom start num n while num: mid num // 2 left sum(coins[bottom:bottom mid]) right sum(coins[bottom mid:bottom mid mid]) if left right: pass # 假币在左半区bottom 不变 elif left right: bottom bottom mid # 假币在右半区起点右移 elif mid * 2 num: return bottom num - 1 # 左右相等且总数为奇数最后一个有问题 else: return None # 左右相等且总数为偶数没有假币 num mid if num 1: return bottom逻辑说明mid是每轮二分的边界left和right分别是左右两堆的重量和。当左边轻说明假币在左边区间起点不动当右边轻起点右移到bottom mid当两边相等就要看当前区间是不是奇数个奇数则最后一个可疑偶数则直接判定没有假币。参数start一般传 0n传len(coins)。这里最容易翻车的是num 1的收尾判断漏掉它会在只剩一枚硬币时陷入死循环。3. 数字拆解与进制转换8-3、8-5、8-6 的工程写法3.1 各位数字之和取模消位法的标准模板8-3 要求计算一个整数各位数字之和。参考实现用的是「取模 整除」的循环def calculate(temp): ans 0 while temp: ans temp % 10 # 取出最低位 temp // 10 # 消去最低位 return ans a int(input(请输入一个整数)) print(calculate(a))逻辑说明temp % 10拿到当前最低位数字累加到anstemp // 10把最低位丢掉。循环直到temp变成 0。参数上要注意负数——如果传入 -123temp % 10在 Python 里得到的是 7 而不是 -3结果会不符合直觉。常见做法是先取绝对值再拆解或者单独处理符号位。这个模板是后面进制转换的基础务必先写熟。3.2 十进制转任意进制反序输出的细节8-5 要求写三个函数分别实现十进制转十六进制、八进制、二进制。三个函数的骨架完全一样区别只在除数和字符映射表def My_Hexadecimal(temp): ans Map {0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7, 8:8,9:9,10:A,11:B,12:C,13:D,14:E,15:F} while temp: ans Map[temp % 16] temp // 16 return ans[::-1] def My_Octonary(temp): ans while temp: ans str(temp % 8) temp // 8 return ans[::-1] def My_Binary(temp): ans while temp: ans str(temp % 2) temp // 2 return ans[::-1] value int(input(请输入一个整数)) print(十六进制0x, My_Hexadecimal(value)) print(八进制0o, My_Octonary(value)) print(二进制0b, My_Binary(value))逻辑说明核心公式是a n % r然后n // r直到n为 0最后把收集到的余数反序。十六进制多了一张映射表因为 10 到 15 要显示成 A 到 F。参数r就是进制基数改 16、8、2 分别对应三种输出。这里有个边界输入 0 时三个函数都会返回空字符串因为while temp直接不进入循环。常见做法是在函数开头加一句if temp 0: return 0。3.3 可变参数一个星号解决不特定多个数相乘8-6 要求写一个函数计算一个或不特定多个数的乘积参考实现用了*tempdef calculate(*temp): ans 1 for x in temp: ans * x return ans print(calculate(1, 2, 3, 4, 5)) # 120逻辑说明*temp把调用时传入的所有位置参数打包成一个元组函数体内遍历这个元组累乘。初始值必须是 1 而不是 0否则结果永远是 0。参数上这个函数可以接收任意个数包括零个——零个参数时返回 1这是乘法单位元的合理定义。如果你需要同时支持关键字参数可以用**kwargs但本题只要求位置参数*temp就够了。4. 回文素数筛选与折半查找8-7、8-8 的算法取舍4.1 素数筛选法为什么比逐个判断快8-7 要求打印前 100 个回文素数每行 10 个。参考实现没有用「对每个数试除到平方根」的常规做法而是用了素数筛选法def Prime(): prime [] count {} for i in range(2, 100000): temp i if temp not in count: prime.append(temp) while temp 100000: count[temp] 1 temp i return prime def judge(temp): value_str str(temp) Len len(value_str) for i in range(Len // 2): if value_str[i] ! value_str[Len - 1 - i]: return False return True prime Prime() ans [] count 0 for i in prime: if judge(i): ans.append(i) count 1 if count 100: break cnt 0 for x in ans: print(x, end ) cnt 1 if cnt % 10 0: print()逻辑说明Prime()用字典count标记合数外层循环每遇到一个未被标记的数就收进prime列表然后从它开始按自己的步长向后标记。这样每个合数只会被标记一次整体接近线性。judge()用双指针从字符串两端向中间比对判断回文。参数上range(2, 100000)的上界决定了候选池大小前 100 个回文素数远小于这个范围所以够用。这里要注意筛选法的内存占用随上界线性增长上界开到千万级时字典会吃掉大量内存常见做法是改用字节数组或bytearray。4.2 折半查找的边界条件比循环体更容易写错8-8 给了一个学生列表要求用折半查找按学号找学生。参考实现如下def Binary_Find(Stu): bottom 0 top len(Stu) - 1 while bottom top: mid (bottom top) // 2 if num int(Stu[mid][0]): return 已找到 elif num int(Stu[mid][0]): bottom mid 1 elif num int(Stu[mid][0]): top mid - 1 return 没有找到 num int(input(请输入要查找的学生学号)) Stu [[str(2018 i), str(i 20)] for i in range(21)] print(Binary_Find(Stu))逻辑说明bottom和top维护当前查找区间mid取中点。找到就返回目标比中点大就把下界抬到mid 1比中点小就把上界压到mid - 1。循环条件是bottom top等号不能丢否则区间只剩一个元素时会漏查。参数上Stu必须按学号递增有序这是折半查找的前提。参考实现里Stu是用列表推导生成的模拟数据学号从 2018 到 2038姓名是编号加 20 的字符串。注意参考代码里else分支挂在while外面实际运行时如果循环正常结束会走到return 没有找到逻辑上没问题但写法上更清晰的方式是把return 没有找到放在循环之后、不加else。5. 避坑与排查这 9 道题里最容易翻车的 5 个地方5.1 函数内赋值导致 UnboundLocalError现象函数里先print(x)再x 10运行报UnboundLocalError: local variable x referenced before assignment。原因Python 在编译函数时发现x被赋值就把它整个函数体内都当局部变量打印时它还没绑定值。解决要么把赋值去掉只读全局要么在函数开头加global x声明要么换个变量名。我一般建议换名global用多了代码会变得很难追踪。5.2 递归没有终止条件或终止条件写错现象程序跑起来报RecursionError: maximum recursion depth exceeded或者结果比预期多一项少一项。原因终止条件没覆盖所有分支或者步长和终止值不匹配。解决先把递归展开式手工写三层确认每一层都在向终止条件靠近再用小输入比如func(3)验证结果。8-4 那道题如果终止条件写成x 1传入偶数就会无限递归。5.3 进制转换忘记反序或漏掉 0现象输入 10 转二进制得到0101而不是1010或者输入 0 得到空字符串。原因取模是从低位到高位收集的必须反序while temp在temp为 0 时不进入循环。解决返回前统一[::-1]并在函数开头补if temp 0: return 0。这个坑在 8-5 里三个函数都会遇到改一个别忘了另外两个。5.4 回文判断用反转字符串但没处理负数或前导零现象判断-121时得到「是回文」但负数按定义不是回文数。原因str(-121)反转后是121-和原串不等其实会返回 False但如果先取绝对值再判断就会出错。解决回文数判断前先排除负数或者明确题目只考虑正整数。8-7 的回文素数都是正整数所以参考实现没处理负数但如果你把judge复用到别处这个边界要补上。5.5 折半查找的 mid 计算和区间更新不匹配现象查找某些元素时死循环或者明明存在却报「没有找到」。原因mid用(bottom top) // 2时如果更新写成bottom mid而不是bottom mid 1区间可能不再缩小。解决记住配套写法——左边界更新用mid 1右边界更新用mid - 1循环条件用bottom top。这三者是一套的改一个就要检查另外两个。6. 把这份答案用出最大价值我的验证习惯和一道进阶改写拿到一份课后题答案最没用的用法就是抄一遍交上去。我自己的习惯是先把答案盖住自己写一版跑通之后和参考实现对比重点看三个地方——边界条件、参数命名、循环终止。这份第 8 章的答案里8-4 的递归和 8-9 的递归改非递归是最值得动手改写的两道因为它们直接对应「函数调用栈」这个核心概念。以 8-4 为例参考实现是递归版我一般会强制自己再写一个循环版然后对比两者在输入规模变大时的表现def func_iter(x): ans 0 while x 1: ans x x - 2 return ans print(func_iter(9)) # 25逻辑说明循环版用ans累加x每次减 2直到小于 1。参数和递归版一致但没有了函数调用栈的开销。输入 9 时两者都得 25输入 99999 时递归版可能触发递归深度限制循环版则毫无压力。这就是「递归改非递归」在实际工程里的意义——不是炫技而是绕开解释器的栈深度约束。再进一步我会给这类函数补一组最小验证用例用assert把边界钉死assert func_iter(1) 1 assert func_iter(2) 1 assert func_iter(9) 25 assert func_iter(0) 0逻辑说明func_iter(1)验证最小奇数输入func_iter(2)验证偶数输入只累加 1func_iter(9)验证题目原例func_iter(0)验证零输入返回 0。这四个用例覆盖了奇数、偶数、零三种边界。我建议你拿到这份资源后对 8-3、8-5、8-6 也各补一组这样的断言跑一遍再交作业能挡掉大部分「本地看着对、换个数就崩」的情况。从那以后我每次拿到一份带答案的练习题都强制自己先写测试用例再对答案而不是直接看参考实现。这个习惯帮我省掉了太多「以为看懂了、其实没懂」的后悔药。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询