Python笔试输入输出实战:从标准输入到sys.stdin.buffer的高效写法 先讲一个很多刷题党都经历过的场景笔试给你两小时前面那道题你五分钟就想清楚了算法思路结果卡在读取输入上——样例怎么都读不对调了半天才敢写核心逻辑最后时间不够用能跑的代码也来不及提交。这种憋屈刷过题的基本都懂。python编程的笔试里输入输出看着是基础实际上它是第一道真正的门槛评测机只认标准输入和标准输出你的代码逻辑再漂亮I/O 写得不得体照样给你一个红色的大叉。这篇文章不聊算法设计专门聊笔试里的输入输出把最常见的读取场景、输出格式、性能取舍和排雷经验一次性讲透适合正在准备笔试和面试、但总在输入输出环节翻车的同学参考。1. 笔试的裁判是什么先弄懂评测机怎么判你的代码1.1 标准输入、标准输出与“面向输出”的判题方式我见过不少第一次参加线上笔试的同学拿到题目第一反应是打开文件、读文件、写文件有的甚至写死了一个路径去读in.txt。这其实不怪他们平时写脚本习惯了文件读取但笔试评测机和本地脚本不一样。在线评测系统做的事情非常简单它把预先准备好的测试数据当作标准输入喂给你的程序然后捕获你的程序写到标准输出的内容再和标准答案做比对。整个过程不涉及文件路径不需要你打开任何文件只要你的程序老老实实从stdin读、往stdout写就没问题。所以第一课是你的程序永远不要自己去猜数据文件在哪也不要试图读取某个路径直接用输入函数从标准输入拿数据。在这个前提下输入输出的代码就成了一种固定套路你练熟了遇到新题只是套模板而已。这里还要理解评测机比对答案的方式。不同平台的比对策略不完全一样但大体上它会把你的输出逐字符和标准答案比对。行尾多余的空格、中间漏掉的换行、多余的空白字符都可能直接导致答案错误。有些平台会忽略行尾空格和末尾换行但绝大多数平台不会帮你合并多行也不会自动补全缺少的空格。换句话说你的输出必须满足题目要求的格式差一个空格都可能有风险。1.2 四类最常见的输入输出约定我刷题和辅导别人刷题这些年把笔试里的输入输出模式总结成了四类你先把这四类认清楚后面读代码就不会慌第一类单行输入多个值比如“第一行包含两个整数 a 和 b”。第二类多行定长输入比如“第一行一个整数 n接下来 n 行每行 m 个数”。第三类不定行数输入直到文件结束EOF比如“多组测试数据每组一行处理到文件结束”。第四类带格式的输入比如用逗号、冒号分隔或者带前缀字符串的输入需要额外加工。题目通常在描述里会写得很明确但很多人读题只盯着算法部分把开头几行的输入约定扫一眼就过去了等到写代码时才发现读不进去。我的习惯是反过来先看输入输出约定再决定怎么读、怎么组织数据结构最后才看算法本身。这个顺序真的很重要。2. 读取方式怎么选input 与 sys.stdin 的正确用法2.1 input() 的底层逻辑和适用边界Python 初学阶段接触最多的就是input()。它的用法很简单input()会把读到的内容作为字符串返回并且去掉末尾的换行符。比如输入一行1 2 3input()返回的是字符串1 2 3不是列表也不是数字你需要自己处理。但很多人不知道input()的底层其实就是封装了sys.stdin.readline()差别在于它会把末尾的换行符去掉并且内部做了字符串解码。因此在数据量小的时候用input()完全没问题代码也简洁。什么时候该换用sys.stdin我的经验是当你觉得自己可能要处理几千行、几万行甚至更多数据时input()的性能就不够看了。它每调用一次都要经历一层封装和字符串处理循环里成千上万次调用累计开销很可观。真实笔试里有些题目数据量能到十万行、百万行此时input()可能让你的程序超时。这里要补一个底层知识sys.stdin本身是一个文本流对象而sys.stdin.buffer是它的底层字节流。直接读字节流省掉了字符解码的环节性能明显更好。你在字节流上 read 得到的是 bytessplit()之后得到的也是字节片段但好消息是int()可以直接处理bytes所以map(int, data.split())这种写法依然成立。2.2 sys.stdin.buffer大数据量下的性能利器我在本地模拟过一个场景生成了 100 万行整数数据每行 3 个数字分别用input()逐行读和sys.stdin.buffer.read()一次性读再做split()。实测下来input()那套方案跑了九百多毫秒sys.stdin.buffer.read().split()方案只要两百毫秒左右。数据量越大差距越明显。所以我的选择标准很朴素数据量小、行数少、只是为了快速写题直接用input()简单可靠代码可读性高。数据量可能很大或者题目的测试数据祖传变态上sys.stdin.buffer。这里并不是说input()一定会超时而是说能用一行代码避免性能风险为什么不直接用笔试环境里运行时间卡得很死的时候早一秒跑完就多一分保险。2.3 三个可复用的读取模板为了方便我把常用的读取模板整理成了三种你可以根据题型直接套用。模板一一次性读取所有数据适合先拿到完整数据再处理的题目。import sys def solve(): data sys.stdin.buffer.read().split() # data 是字节片段的列表比如 [b3, b5, b1, b2, b3] # 需要时可以直接 int(x) 转换 # 接下来按数组下标取数即可 it iter(data) n int(next(it)) # ...模板二按行读取适合需要逐行判断、逐行处理的题目。import sys for line in sys.stdin.buffer: # 每行都是 bytes注意末尾带有换行符 line line.strip() if not line: continue a, b map(int, line.split()) # 处理逻辑模板三用iter(callable, sentinel)逐行读到 EOF适合需要手动控制循环的场景。import sys readline sys.stdin.buffer.readline for line in iter(readline, b): line line.strip() # 处理逻辑这个iter(callable, sentinel)算一个冷门但好用的技巧。它不断调用readline直到读到的内容等于哨兵值b也就是 EOF时停住。比起while True: line readline(); if not line: break这种写法它少两行也不容易漏 break我强烈建议你把它记下来。3. 输入场景拆解从单行定长到矩阵和不定行数3.1 单行多值strip、split、map 三件套笔试里最常见的输入就是“一行多个值”。比如题目写“第一行包含两个整数 a 和 b”那你写a, b map(int, input().split())这行代码拆开来看是三层split()默认按空白字符分割连续的空格、制表符、换行符都会被当成同一个分隔符map(int, ...)把分割后的字符串列表逐个转成整数最后解包赋值给a和b。绝大多数“一行 N 个数”的输入一行代码就能解决。但这里有几个坑。第一split()返回的是一个可迭代对象如果你的输入行有前导空格或结尾空格split()会把它处理干净不需要额外strip()。第二如果你遇到的是“每行一个数字”不要写int(input())之外的花哨代码直接int(input())就行。第三如果一行要读入的值个数不固定不能硬解包先接成列表再说nums list(map(int, input().split()))我经常看到同学在“一行多个数”的地方非要循环读读半天读几行也不对其实根源就是没有意识到一行可以一次性全部读完。3.2 行数已知列表推导式读矩阵另一类高频输入是矩阵或者二维数组。题目会给出行数 n 和列数 m紧接着给 n 行数据。这种情况下最简洁的读法是列表推导式n, m map(int, input().split()) matrix [list(map(int, input().split())) for _ in range(n)]一行一行读进来每行按空格分割后转成列表最终得到嵌套列表。这个写法效率不错代码也短。如果你的矩阵元素不是数字而是字符比如3 3 ### #.# ###那就不需要split()了直接list(input().strip())就能把每行拆成字符列表n, m map(int, input().split()) grid [list(input().strip()) for _ in range(n)]这里strip()的作用是去掉行尾的换行符不然list()会把\n也拆进去后面处理起来全是坑。关于矩阵还有一个常见痛点有些题目的输入里行末可能有多余空格比如“1 2 3 ”这样。用split()会自然忽略末尾空格不用担心。3.3 行数未知EOF 循环的两种标准写法不定行数这种题型出镜率极高因为评测系统没有告诉你有多少组数据你的程序必须自己判断什么时候读完。标准做法是读到 EOF 就停。第一种写法利用sys.stdin.buffer的迭代特性import sys for line in sys.stdin.buffer: line line.strip() if not line: # 跳过空行很多人会在这里纠结 continue # 处理一行数据第二种写法用while配合input()捕获异常while True: try: line input() except EOFError: break if not line: continue # 处理一行数据我个人更推荐第一种。它天然适配 EOF 语义代码量也少。第二种适合那些必须知道“这是第几行”或者要在循环里做特殊处理的场景但它依赖EOFError异常这种风格有人觉得绕有人觉得稳你自己选顺手的就行。还有一个小细节如果题目保证每一行都有数据不会出现空行那么if not line: continue这个判断其实可以省略。有些平台会在文件末尾多放一个空行这时不处理空行可能导致int()转换报错所以我习惯保留这个判断。3.4 奇怪格式输入的处理技巧笔试里偶尔会出现花式输入比如一行数据是1,2,3,4,5或者a1, b2这种情况下split()就不好使了需要先做一次字符替换。我的处理顺序是先看分隔符是什么再用replace()把特殊分隔符统一替换成空格最后走标准流程。以逗号分隔的数字为例line input().strip() nums list(map(int, line.replace(,, ).split()))如果分隔符是,但元素前后还带着空格比如1, 2, 3上面的写法依然有效因为split()会把多余空格忽略掉。如果格式是a1, b2那就更花哨一些一般用正则表达式提取数字比较省事import re nums list(map(int, re.findall(r\d, input())))这种题不多见但一旦遇到re.findall能救你一命。它直接提取字符串里所有数字省去手动 split 的麻烦。要注意如果题目里还有负号你需要把正则改成-?\d。4. 输出的隐形扣分点从 print 到 join 与格式化4.1 print 默认行为带来的三个坑print()在笔试里也有讲究。第一print()默认在末尾追加换行符这本身没问题问题是很多人不知道它一次输出多个参数时会用空格分隔。比如print(1, 2, 3)输出的是一行1 2 3中间有空格。如果题目要求每两个数字之间以空格分隔那这个行为刚好符合要求如果要求连续输出就不能这么写。第二print()默认结尾是\n如果题目要求一行末尾不要换行你需要用end或者end 来手动控制。但我要提醒你绝大多数笔试题目不要求你输出到“一行无换行”你正常换行反而更安全。第三如果你想在循环里逐行输出最忌讳的是疯狂调用print()。数据量大时输出本身也是一种开销频繁调用print()会拖慢程序。更好的做法是先把所有输出内容收集到一个列表里最后一次性用\n.join(lines)输出或者直接sys.stdout.write(...)。4.2 批量输出用 join 统一管理格式我最推荐的输出策略是“收集后统一输出”。比如你要输出多行结果可以这样res [] for i in range(n): # 计算当前行结果 res.append(fCase #{i1}: {ans}) sys.stdout.write(\n.join(res))注意这里join之后通常不需要再加末尾换行。很多评测平台对末尾换行的容忍度不同但你不加末尾换行也没问题加了也不扣分。真正会被扣分的是该换行的地方没换行该空格的地方没空格。再举一个常见的坑输出矩阵时每行元素之间以空格分隔行尾不能有多余空格。有人写出这样的代码for row in matrix: for x in row: print(x, end ) print()这样每行末尾会多一个空格风险很大。用join就不会出这个问题for row in matrix: print( .join(map(str, row)))join天然保证行尾没有多余空格这是我最喜欢它的原因。4.3 浮点数精度与对齐输出涉及到浮点数输出时题目通常会明确说“保留两位小数”或者“四舍五入保留三位小数”。这时候不要用round()去碰运气直接格式化。Python 3 里最舒服的写法是 f-stringprint(f{value:.2f})它支持四舍五入到指定小数位配合f{value:8.3f}还能做占位对齐。如果你在做老项目用format()写法是{:.2f}.format(value)效果一样。还有一个隐藏细节输出大量浮点数时格式化操作本身也有性能开销。如果数据量巨大建议把所有格式化后的字符串收集到列表里再一次性输出而不是边算边 print。格式化后的结果已经是一个字符串放在列表里没问题。5. 实测排雷本地没问题提交为什么错了5.1 换行符、多余空格和隐藏字符排雷环节我先讲一个我自己踩过的坑。有段时间我在某个平台答题本地跑样例一切正常但提交后频繁答案错误。排查了半天最后发现是我读入的数据每行末尾带着\r。问题出在测试数据是用某些工具生成的行尾是\r\n我的代码在split()时没遇到问题但凡是需要对字符串本身做处理的题比如统计字符、比较字符串就全乱了。解决方案很简单读一行之后先line.strip()再处理。strip()会去掉行尾的\r和\n也能去掉行首空格。加不加这一行遇到特殊数据时就是天壤之别。所以我的统一原则是凡是按行读的字符串先 strip 再往下走。凡是按空格拆的直接用split()不用 strip。5.2 调试输出污染 stdout这是另一个经典事故。本地调试时为了看中间结果写了一堆print(中间值, x)代码提交前忘了删评测系统把你的调试信息当成答案输出当然答案错误。我的土办法调试信息写进sys.stderr这样本地能看到输出评测系统捕获 stdout 时不会被污染。import sys print(debug:, val, filesys.stderr)如果你确实用print调试提交前就用“查找替换”把所有print(全删掉或者干脆统一加注释标记。总之输出口要控制好。5.3 本地运行正常但提交报错的排查清单当你遇到“样例全过但提交报错”时按照下面这个顺序排查检查代码里有没有操作本地文件路径。检查有没有未删除的调试输出。检查读取输入时有没有strip()或者split()处理不当。检查循环里有没有死循环风险比如while True但缺少退出条件。检查递归深度是不是爆栈了。检查数据量大的时候input()是否导致超时。大部分“本地能跑提交就挂”的问题基本逃不出上面几条。5.4 常见问题速查表现象可能原因解决办法答案错误本地却通过输出行尾有额外空格或换行不合理用join生成输出检查每行内容答案错误且输出乱码strip()没调用\r被当成数据读字符串后统一strip()运行时错误读取时遇到空行int()转换失败加if not line: continue运行时错误索引越界读取的数据比预期少检查split()后长度必要时打印看一下超时数据量大但用input()逐行读改用sys.stdin.buffer.read()超时循环内频繁print()收集结果最后一次性输出答案错误调试print()污染 stdout写入sys.stderr或提交前删除这张表我建议你收藏笔试前翻一遍能省不少冤枉时间。6. 实例拆解两道典型题目的完整输入输出代码6.1 经典求和多组数据直到 EOF题目描述通常是这样的“输入包含多组测试数据每组数据一行包含两个整数 a 和 b处理到文件结束为止对每组数据输出 ab。”这道题的标准输入输出代码用模板二就能解决import sys def solve(): out_lines [] for line in sys.stdin.buffer: line line.strip() if not line: continue a, b map(int, line.split()) out_lines.append(str(a b)) sys.stdout.write(\n.join(out_lines)) if __name__ __main__: solve()注意我先把结果放进了out_lines最后一次性输出。这样做的好处是无论有多少组数据输出格式都稳定也不会因为频繁print()拖慢速度。如果题目要求读到0 0结束那就在循环里加一个判断for line in sys.stdin.buffer: line line.strip() if not line: continue a, b map(int, line.split()) if a 0 and b 0: break out_lines.append(str(a b))这里的break和continue要分清楚continue跳过错觉为空的行break才是真正退出整个循环。6.2 矩阵转置输出格式控制的完整示范题目描述“输入第一行两个整数 n 和 m接下来 n 行每行 m 个整数输入矩阵的第 j 行第 i 列元素是原矩阵第 i 行第 j 列元素输出转置后的矩阵。”读矩阵和输出转置的完整代码import sys def solve(): data sys.stdin.buffer.read().split() if not data: return it iter(data) n int(next(it)) m int(next(it)) matrix [] for _ in range(n): row [int(next(it)) for _ in range(m)] matrix.append(row) out_lines [] for j in range(m): col [str(matrix[i][j]) for i in range(n)] out_lines.append( .join(col)) sys.stdout.write(\n.join(out_lines)) if __name__ __main__: solve()这里我直接用read().split()把所有数字一次性读出来再通过iter依次取数。不管输入分成几行、中间有多少换行全部被split()抹平了程序根本不受换行影响。这种做法在矩阵类题目里格外好用。输出转置矩阵时关键是“按列取元素”拼成新的行用join控制行内空格最外层join控制行与行之间的换行。整个输出格式稳如泰山。结尾刷题刷到现在我的体会是笔试里的输入输出练的不是“会不会用函数”而是“能不能把题目的描述转成稳定的代码习惯”。我建议你专门花几个晚上把本文里的模板全部敲一遍不要复制一行一行自己写写到不用思考就能默写的程度。等你上手了就会发现所谓卡输入输出其实是把精力浪费在了不该浪费的地方。把这部分固定下来你的注意力才能真正放到算法上笔试也才有底气。再退一步真遇到没见过的输入格式记住一个原则先 strip再 split能用 buffer 就不 input能 join 就别手工拼字符串。按这个思路走输入输出这道坎你跨过去只是时间问题。