刷题刷到第90多道是什么感觉微信上有个读者跟我抱怨Python小屋的题他每道都能写出来可一看参考解答总觉得自己的代码又臭又长像在拼积木人家写的却像在盖房子。这问题太典型了。Python小屋剧本里的编程题91-100恰好就是这种分水岭式的存在前90道帮他把语法地基打牢到了这批题考察重心从这个函数会不会用悄悄转向这个场景你选什么工具、怎么写才不返工。这篇文章就聊我刷完董付国老师Python小屋编程题91-100之后的复盘心得。面向的读者是已经掌握Python基础语法、准备进阶刷题或者正在准备考试笔试的在校生和转行求职者。我会把这一批题涉及的高频知识点拆开揉碎一个个讲清楚为什么这么写、坑在哪、怎么避。1. 编程题91-100在Python小屋系列中的定位从语法会了到代码能扛事1.1 这一批题为什么是道分水岭Python小屋的题目是按天推进的前几十道基本是单个知识点的定点清除比如今天考字符串切片明天考列表推导式后天考文件读写。但到了91-100这个区间题目风格明显变了——它不再问你某函数有哪些参数而是直接扔一个真实场景让你自己判断该用什么数据结构、什么写法最稳妥。我印象最深的是这一批里有好几道题表面看是字符串处理实际上一不留神就会写出O(n^2)的代码还有几道题看似在考函数定义真正考的其实是默认参数的求值时机。这种题面很简单坑全在细节里的风格对从初学者过渡到初级开发者的人来说特别有价值。能按部就班写出答案说明你学会了语法能一眼看出题目想考什么、主动避开常见陷阱说明你开始有了工程意识。1.2 十道题的考察重心先画一张知识地图我不建议稀里糊涂地一道一道刷。刷之前先看整体把十道题考察的知识点归归类心里有个地图刷起来才有方向。整理一下这批题涉及的核心主题题目类型涉及核心知识点常见翻车点字符串处理类切片、去重保序、文本统计用set后顺序乱了函数进阶类默认参数、可变对象、闭包默认参数用可变对象导致状态污染数据聚合类字典计数、排序、zip不会用sorted的key参数做多级排序面向对象类类变量/实例变量、魔法方法对__str__和__repr__的职责边界不清楚综合应用题枚举、拆包、递归、推导式代码能跑但复杂度感人这十道题其实串起了一个代码能不能扛事的能力链路从处理文本到封装逻辑再到组织数据、设计类。每一个环节都是后续做小项目、参加笔试面试的底子。2. 字符串与文本处理的几道题能跑通只是及格写法才见功力2.1 去重保序set 一时爽顺序火葬场这种题太经典了给定一个字符串去掉重复字符并保持字符第一次出现的顺序。很多人的第一反应是转set去重一跑发现顺序全乱了因为set本身是无序的。我当时的做法经历了三个阶段。第一阶段是无脑set翻车。第二阶段是老老实实用循环def dedupe(s): result [] seen set() for ch in s: if ch not in seen: seen.add(ch) result.append(ch) return .join(result)第三阶段才反应过来这其实是字典有序的天然应用场景。Python 3.7以后字典保持插入顺序直接用dict.fromkeys就能一行搞定def dedupe(s): return .join(dict.fromkeys(s))dict.fromkeys会把字符串里每个字符作为键插入字典重复的键自动被忽略而且键的顺序就是首次出现的顺序。这个写法背后是利用字典键的唯一性 有序性的组合拳。踩过一次坑之后我才明白这类题考察的不是你会不会set而是你能不能根据需要去重但必须保序这个约束条件快速选出合适的数据结构。顺序敏感就往有序结构上靠不敏感才考虑set。2.2 回文判断的三种写法与复杂度对比回文题在91-100这批里也出现了不止一次。最简单的写法是字符串反转后比较def is_palindrome(s): # 忽略大小写和空格 s .join(ch.lower() for ch in s if ch.isalnum()) return s s[::-1]更省事的办法是双指针从两端往中间走遇到非字母数字就跳过def is_palindrome(s): i, j 0, len(s) - 1 while i j: while i j and not s[i].isalnum(): i 1 while i j and not s[j].isalnum(): j - 1 if s[i].lower() ! s[j].lower(): return False i 1 j - 1 return True两种写法都能跑但面试笔试里双指针版本的价值在于它压根不需要额外存储空间O(1)空间复杂度而且在字符流式输入的场景下更实用。Python里切片反转虽然优雅但会创建新的字符串对象数据一长内存消耗就上去了。多掌握一种写法笔试遇到不能开辟额外空间的附加条件时你心里不慌。2.3 格式化输出f-string 的排版细节文本处理题里还藏着一类看起来简单、实际上容易丢分的题格式化输出。比如要求把一组数据按固定宽度、右对齐方式打印成表格。以前不少人还在用%和format现在f-string已经足够好用了data [(苹果, 12), (香蕉, 5), (榴莲, 3)] for name, count in data: print(f{name: 6}{count: 3})这里面有个细节是格式说明符里的对齐和填充字符。{name:6}表示左对齐占6个字符宽度{count:3}表示右对齐占3个字符宽度。如果要求不足位补0就写{count:03d}。我在这些题上最大的体会是格式化不只是把变量塞进字符串宽度、对齐、填充、千分位这些参数都是实用技能做报表、写日志、生成测试数据时天天用得到。提示如果输出结果要求按总宽度居中对齐用^符号比如{name:^10}数字千分位分隔符是{num:,}处理大整数时非常实用。3. 函数进阶题里的几个高频雷区默认参数、可变对象与闭包3.1 默认参数只在定义时求值一次这一批函数题里最经典的一道是考察默认参数在函数定义时只被求值一次。很多人以为默认参数每次调用都会重新计算其实完全不是这样。def add_item(item, lst[]): lst.append(item) return lst print(add_item(1)) # [1] print(add_item(2)) # [1, 2]而不是 [2]第一次调用后默认列表lst已经被改造成了[1]第二次调用时用的还是同一个列表对象于是变成了[1, 2]。这个行为对没踩过坑的人来说非常反直觉。标准写法是把可变默认参数改成Nonedef add_item(item, lstNone): if lst is None: lst [] lst.append(item) return lst我当时做题时也纳闷明明每次调用都重新传了参数为什么上次的结果会漏进来后来想通了函数对象在定义时就把默认参数绑定到自己身上了这个绑定关系是持久的。理解这一点比背住不能用可变对象当默认参数这条规则更重要。3.2 闭包与lambda延迟绑定的问题另一道让我印象深刻的题和lambda有关。题目大概是生成一组函数每个函数返回自己的序号结果一跑所有函数都返回了最后一个值。funcs [lambda: i for i in range(10)] print([f() for f in funcs]) # 全是9原因在于闭包捕获的是变量i的引用而不是创建lambda时的值。等到真正调用f()时i已经跑到了9于是所有函数看到的都是9。解决方案有两个一个是立即绑定默认参数funcs [lambda ii: i for i in range(10)]另一个是用偏函数functools.partialfrom functools import partial funcs [partial(lambda x: x, i) for i in range(10)]像这种创建函数列表的题在笔试里特别常见。我建议把延迟绑定这四个字刻在脑子里循环变量在闭包里不会自动快照你想要快照就必须显式绑定。3.3 递归函数里的默认参数缓存技巧这批题里有一道要求实现带缓存的递归函数最直接的思路是用字典手动缓存cache {} def fib(n): if n in cache: return cache[n] if n 2: result n else: result fib(n - 1) fib(n - 2) cache[n] result return result但有经验的Pythoner会想到用默认参数做缓存容器把缓存和函数绑定在一起def fib(n, cache{}): if n in cache: return cache[n] if n 2: result n else: result fib(n - 1) fib(n - 2) cache[n] result return result这个技巧我在做这道题时觉得特别妙默认参数虽然不能用来存可变状态上一节的坑但如果你读懂了它的底层逻辑反而可以利用它实现记忆化。当然正规项目里更推荐用functools.lru_cache装饰器from functools import lru_cache lru_cache(maxsizeNone) def fib(n): return n if n 2 else fib(n - 1) fib(n - 2)从手动cache到默认参数缓存再到lru_cache这个演进过程本身就说明同一道题可以有一百种写法但高手会选既简洁又不易出错的那个。4. 数据聚合与排序字典、Counter 和 sorted 的搭配才是日常主力4.1 词频统计的进化路线编程题91-100里有一道词频统计题要求统计一段文本里每个单词出现的次数并按次数从高到低输出。最常见的写法是手动判断键是否存在word_count {} for word in text.split(): if word not in word_count: word_count[word] 1 else: word_count[word] 1这种写法没错但代码有点啰嗦。稍微进阶一点可以用setdefaultword_count {} for word in text.split(): word_count.setdefault(word, 0) word_count[word] 1再进阶就是defaultdictfrom collections import defaultdict word_count defaultdict(int) for word in text.split(): word_count[word] 1到了这一步代码已经清爽很多了。但如果题目允许使用标准库collections.Counter直接一招解决from collections import Counter word_count Counter(text.split())Counter自带most_common方法可以按出现次数排序输出for word, count in word_count.most_common(): print(word, count)我要强调的是这几种写法在功能上等价但语义逐渐清晰从我自己管理字典键到让容器帮我处理缺失键再到用专用计数器直接统计。写代码的时候脑海里应该有一排可选工具根据场景选最合适的而不是只会最基础的for循环。4.2 sorted 的多关键字排序怎么排和词频统计配套的通常是排序题。比如按出现次数降序次数相同再按字母表升序。很多人到这就卡住了因为reverseTrue会把两个方向都倒过来。正确做法是不要用reverse而是利用key返回一个元组并配合负号对数值取反result sorted(word_count.items(), keylambda item: (-item[1], item[0]))这里的技巧在于-item[1]把降序转化成升序第二个元素item[0]保持字母升序两个条件都满足。如果题目要求不改变原数据直接用sorted生成新列表如果数据量特别大也可以用list.sort原地排序省内存。笔试里排序条件千变万化但这个元组key 负号取反的模式是万金油。4.3 zip 和 enumerate 的配合使用有一道题是把两个列表按位置合并成字典比如把姓名和分数组合起来。新手会写for i in range(len(names))我见过更优雅的写法names [Alice, Bob, Charlie] scores [85, 92, 78] score_dict dict(zip(names, scores))zip按索引配对dict直接转成字典一行搞定。如果还想保留序号enumerate配合zip使用for idx, (name, score) in enumerate(zip(names, scores), 1): print(idx, name, score)这种解包写法在真实开发里出现频率极高。刷题时多积累这类组合用法比孤立地背单个函数有用得多。5. 面向对象题暴露的认知盲区类变量、实例变量与魔法方法5.1 类变量和实例变量共享还是独享这批题里有一道关于银行账户的题要求给每个账户生成唯一的账号。很多人的第一版代码会写成class Account: account_id 0 # 类变量 def __init__(self, name): self.name name self.account_id 1一实例化就报错因为self.account_id 1实际上是先读取类变量再创建一个新的实例变量类变量本身没变。正确写法是直接用类名访问class Account: account_id 0 def __init__(self, name): self.name name Account.account_id 1 self.id Account.account_id这个坑的关键在于搞清楚类变量被所有实例共享实例变量只属于当前实例。写成self.account_id 1表面上是修改类变量实际上Python先查实例属性发现没有就沿继承链找到了类变量然后执行加法再把结果绑定成实例属性。类变量还是原来的值。我分享一个经验调试这类问题最快的方式是打印__dict__实例属性和类属性分别存在哪一眼就能看出来。5.2str和repr的职责边界面向对象题里常见的一个要求是打印对象时输出友好格式。新手通常只写__str__但默认情况下列表里的对象还是显示成内存地址。这里涉及两个魔法方法的职责边界__str__是给人看的__repr__是给开发者看的repr要尽量无歧义、能还原对象状态。class Account: def __init__(self, name, balance): self.name name self.balance balance def __str__(self): return f账户({self.name}, 余额{self.balance}元) def __repr__(self): return fAccount({self.name}, {self.balance})一个很实用的写法是让__str__调用__repr__或者反过来保证两个方法至少有一个完整实现。调试的时候在列表里print(accounts)会调用元素的__repr__而直接print(account)调用__str__。两者都写清楚输出调试信息时才不会一脸懵。5.3 property 让属性访问变体面有一道题要求账户的余额不能被随便改成负数。如果只写普通属性外部直接赋值根本拦不住。这时候应该用property做只读或带校验的属性class Account: def __init__(self, name, balance): self._name name self._balance balance property def balance(self): return self._balance balance.setter def balance(self, value): if value 0: raise ValueError(余额不能为负数) self._balance value表面上看只是多了两个装饰器实际效果是外部代码仍然用account.balance 100赋值但背后自动做了校验。这种外部用法不变内部逻辑增强的特性正是property的最大价值。做题时可能不觉得但设计真实业务类的时候这种边界保护几乎是刚需。初学面向对象时容易陷入一个误区以为写class就是封装。其实封装的意义在于控制访问边界把不该暴露的内部状态藏起来把需要校验的入口收紧。这些题就是提前让你体会这一点的。6. 刷完这十道题之后复盘比继续刷更重要6.1 我的复盘表格模板刷题最容易陷入的误区是刷完对答案、看懂了、下一题。翻完91-100的参考解答后我整理了一个简单的复盘模板每道题都填一遍题目我的第一版解法参考解答的解法差异点暴露的薄弱点例去重保序set 循环dict.fromkeys数据结构意识不足对字典有序性不敏感例函数列表for循环内部lambda默认参数绑定不理解闭包绑定时机作用域知识有漏洞重点是第三列和第四列。如果第一版解法在功能上是正确的但代码啰嗦、效率低那就是知道但不够熟如果直接写错了那就是理解有偏差需要回到对应章节重新看。6.2 举一反三的三种变形练法复盘之后我会把每道题做三种变形这样才算真正吃透第一改数据结构。原题用列表改成字典、集合、双端队列会变成什么写法性能差异多大第二改约束条件。原题没限制空间改成不能使用额外空间做一遍原题数据量小改成十万条数据感受一下性能瓶颈。第三改输出形式。函数改成生成器逐步yield结果调用方怎么适应以词频统计为例原题输出前十高频词变形后要求输出出现频率低于某个阈值的所有词或要求用生成器逐条产出结果考察点瞬间就不一样了。这种练法比盲目刷下一套题有效得多。6.3 刷题之外的收获把题目的场景映射到真实项目最后说点刷题之外的体会。Python小屋这些题虽然以练习为主但很多场景在真实开发里都能找到投影去重保序在爬虫清洗URL时用得上词频统计在分析用户评论关键词时是基本功带缓存的递归在动态规划里是常客类的属性校验在写ORM模型时几乎天天用。我个人的建议是刷题别只盯着这题我会不会而是时刻问一句如果这个逻辑出现在我的项目里我会不会这么写一旦开始这样想刷题就从任务变成了训练。编程题91-100只是一个小阶段但它让我从写得出开始向写得好迈进这个转变的价值远比十道题本身要大。
企业数字化 ERP 产品动态
相关推荐
C# API限流计数一次扣2?从请求重复与中间件顺序定位修复 C# API项目里出现X-Rate-Limit-Remaining一次请求直接减2,这个问题我最近一个月里被问到了好几次。AspNetCoreRateLimit、.NET内置的RateLimiter,甚至自己写的简单计数中间件,都可能出现同一个表象:前端明明只点击了一次ÿ… · 2026/9/25 13:56:17
Docker封装GPU推理环境:从CUDA冲突到容器化部署实战 说实话,我一开始对“把 GPU 推理环境塞进 Docker”这件事是抗拒的。当时我维护一台多人共用的 GPU 服务器,PyTorch、CUDA、cuDNN、TensorRT 的版本全靠人工协调,某天同事在~/.bashrc里改了一行 CUDA 路径,整个小组的推理服务全部起… · 2026/9/25 13:56:11
戈壁母亲剧情全解析:用TaoToken统一Key梳理人物关系与剧情脉络 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 14:24:58
麒麟系统密钥环弹窗根因与禁用全方案 1. 为什么麒麟系统总在“偷偷”弹出密钥环?——从用户行为到系统机制的底层归因你刚打开浏览器访问一个HTTPS网站,或者双击启动企业微信、钉钉这类需要记住密码的应用,屏幕右上角突然跳出一个半透明弹窗:“应用程序想要创建一个默… · 2026/9/25 14:24:40
SQL Server学生选课系统数据库设计:从建表到存储过程完整指南 简介:这份资源是面向计算机相关专业在校学生与教师的SQL Server学生选课系统数据库课程设计完整包,已获导师认可并在答辩中取得95分,适合作为课程设计、期末大作业或项目初期立项的参考模板。压缩包共6个文件,约139KB,… · 2026/9/25 14:24:40
基于Python的某市公交线路客流可视化分析系统设计与实现 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片!
一、 项目背景与意义
随着城市规模的不断扩大和公共交通网络的日益复杂,公交系统作为城市交通的“大动脉”,其运营效率和服务质量直接影响着市民… · 2026/9/25 14:24:33
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37