很多刷题的朋友都遇到过这样一个诡异场景本机测试怎么跑都正常一提交到在线评测系统就直接报错或者卡在某个用例上迟迟不出结果。排查到最后发现问题不是算法思路错了而是败在了最不起眼的读取输入数据这一步。ACM算法题、各类技术笔试、考研机试凡是走在线判题OJ这条路几乎都用标准输入输出打交道。Python在算法题里的读取输入数据操作看着简单里面却有不少门道比如EOF怎么处理、超大数据量怎么读、字符串和字符矩阵怎么解析、读进去的数据要不要去空格换行。这篇文章把我在实际刷题和笔试中总结的常用操作全部摊开来说给出可直接照抄的写法也讲清楚每一步背后的原因。1. 输入读取的核心概念为什么sys.stdin是绕不开的主角初学Python刷算法题的人多半是从 input() 函数写起的。input() 确实是读取一行输入的便捷方法逻辑上也符合直觉你叫它一声它就把当前这一行拿回来给你。它内部其实是调用 sys.stdin.readline() 来实现的区别在于 input() 会自动去掉末尾的换行符把内容作为字符串返回。早期刷题、写一些简单的模拟题input() 完全够用。但ACM模式的题目和数据量一大input() 的问题就暴露了。它每次只读一行而且内部还会做类型转换的准备工作执行效率不如直接操作 sys.stdin 来得高。在数据量达到几十万行、上百万行的题目里这个差距会直接导致程序超时。所以我个人的习惯是只要题目涉及大量输入或者我懒得预判数据量大小就直接上 sys.stdin。sys.stdin 是Python标准输入文件对象它有两种常用的读取姿势sys.stdin.readline()一次读一行包括行尾的换行符。sys.stdin.read()一次性读完所有内容返回一个大字符串适合全部数据一次拿全再慢慢解析。sys.stdin.buffer.read()按字节读入返回的是 bytes 类型常用于需要更高性能的场景。除了效率差异sys.stdin 还天然适合处理不确定行数的输入。很多ACM题不会告诉你一共输入多少行只在最后给一个EOF标志。你用 input() 去一行行读遇到空行或者循环条件判断不当很容易死循环而用 for line in sys.stdin 这种写法读到文件末尾会自动结束循环省心很多。这里有一个新手特别容易误解的点Python里 sys.stdin 并不是“从键盘读”而是“从标准输入流读”。在线评测系统把测试用例作为输入重定向给你的程序相当于把你的程序当作一个处理器标准输入里就是一行行的测试数据程序结束时把所有输出写到标准输出评测系统拿去和标准答案比对。理解了这层机制你就能明白为什么刷题时所有输入都围绕 sys.stdin 展开而不是像写GUI程序那样去监听键盘。2. 常见的ACM输入模式五种题型场景的标准写法刷题这么久我总结下来ACM模式下的输入场景其实就那么几类。把每一种的标准写法烂熟于心读题后就不用再纠结解析代码把脑力全部留给算法本身。2.1 单行单个数据最常见的起步操作这种题目最简单第一行就一个数代表测试次数、数组长度、或者需要计算的数值。import sys n int(sys.stdin.readline().strip()) print(n * 2)有人会问这里为什么非要加 strip()。sys.stdin.readline() 会把换行符也读进来比如输入是5\n如果直接 int(5\n) 会报错。strip() 的作用是把字符串首尾的空白字符空格、换行、制表符去掉。虽然 int() 在转换字符串时本身能容忍前后空白但写成 strip() 是统一习惯后续处理其他类型时能少踩很多坑。2.2 单行多个数据用split一步到位题目给一行里面有多个空格分隔的数比如 1 2 3 4 5。最顺手的写法是import sys data list(map(int, sys.stdin.readline().split()))split() 默认以任意空白字符空格、制表符、换行切分字符串返回一个字符串列表。map(int, ...) 把列表里的每个字符串转成 int再用 list() 包起来得到整数列表。这里有个细节split() 不传参数时会自动处理连续多个空格的情况。比如输入是 1 2 31和2中间有三个空格split() 依旧能切得干干净净。这在从文本里复制粘贴测试用例时特别实用你手动把空格敲多敲少了程序都不受影响。2.3 已知行数的多行数据循环内逐行解析很多题目的结构是第一行给出总行数 N接下来 N 行每行是一组数据。比如矩阵题、图论题基本都是这种形态。import sys n int(sys.stdin.readline().strip()) for _ in range(n): line sys.stdin.readline().split() # 根据每行数据的含义处理 a, b map(int, line) print(a b)这种写法的核心思路是“先用第一行拿到数量再在循环里逐行处理”。由于循环次数是已知的程序不会多读也不会漏读。如果你担心最后一行的换行问题split() 已经帮你去掉换行符了所以不需要额外 strip()。2.4 未知行数直到EOF守护循环大法这是ACM题里最经典的输入模式题目说输入包含多行但没说一共几行读到文件末尾结束。比如处理任意数量的整数对每对求和并输出。写法一用 for line in sys.stdinimport sys for line in sys.stdin: line line.strip() if not line: continue a, b map(int, line.split()) print(a b)写法二用 while readlineimport sys while True: line sys.stdin.readline() if not line: break line line.strip() if not line: continue a, b map(int, line.split()) print(a b)两种写法都能正确处理EOF。细看区别for 循环在文件读完时自动退出简洁不容易出错while 循环则是先读一行发现是空字符串就 break。注意这个空字符串判断很关键readline() 在到达文件末尾时返回空字符串注意它和空行 \n 不是一回事。空行经过 strip() 后是空字符串但 readline() 返回的不是空字符串。所以上面我用了两层判断先判文件结束再判是否空行。2.5 字符串和字符的读取split不一定好用非数值型输入的题目比如处理字符串数组、字符矩阵很多人会本能地用 split()但这里有个大坑。假设题目给了一个 n 行 m 列的字符矩阵每行输入是类似 ABCDE 的连续字符串中间没有空格import sys n, m map(int, sys.stdin.readline().split()) grid [] for _ in range(n): line sys.stdin.readline().strip() grid.append(line) # 每一行作为一个字符串保存如果误用了 split()比如 sys.stdin.readline().split()得到的会是 [ABCDE] 这样一个元素而不是拆开成 [A,B,C,D,E]。你需要的是把字符串转成单个字符列表时用 list(line)row list(sys.stdin.readline().strip()) # 输入 ABCDE得到 [A,B,C,D,E]这种坑在迷宫、搜索、二维DP题目里非常容易踩。我见过不少代码本地测试正常一提交就WA答案错误最后发现是字符矩阵解析多套了一层算法里拿到的根本不是期望的那个字符。3. 类型转换与数据格式化读进来的都是字符串怎么转最顺手既然标准输入本质上读进来的是字符串那所有的结构化数据都需要我们手动做类型转换。这一节总结我在不同场景下使用的转换套路。3.1 一维整数数组最常见的形态一行若干个空格分隔的整数arr list(map(int, sys.stdin.readline().split()))注意事项如果这行数据特别长比如一万个数split() 会产生一个一万元素的列表再转成 int 列表内存开销其实可以接受因为Python的 int 对象本身就有开销但题目通常不会让你挂在这一步。如果对性能有执念可以改用下面这种基于 buffer 的写法import sys data sys.stdin.buffer.read().split() arr list(map(int, data))这里 sys.stdin.buffer.read() 返回的是 bytes 对象split() 按空白切分后得到的是 bytes 元素的列表map(int, ...) 依然能正确转成整数。由于省去了文本解码成 str 的过程大数据量下速度更快。3.2 二维整数列表有的题目先给 n、m然后给 n 行每行 m 个数import sys n, m map(int, sys.stdin.readline().split()) matrix [] for _ in range(n): row list(map(int, sys.stdin.readline().split())) matrix.append(row)另一种常见形态是每行数据长度不一致比如本身就是一个不规则的二维结构那就不能依赖 m 来校验只能按每行实际输入来构造。3.3 带浮点数、大整数、混合类型的数据浮点数用 float() 转换x, y map(float, sys.stdin.readline().split())有些题目会出现大整数比如10^9以上的数Python的 int 是任意精度的因此不需要做特殊处理直接 int() 就行。这在Java或C里可能需要 BigInteger 或 long longPython让这个环节轻松很多。混合类型的行比如第一个是字符串后面是整数parts sys.stdin.readline().split() name parts[0] value int(parts[1])先按字符串切分再根据需要逐段单独转类型。不要一个 map 强行转全部类型map 不会智能到知道哪个位置该转什么。3.4 需要去掉首尾空格的数据行有些输入行的末尾有额外空格或者开头有缩进这在读取普通字符串时会影响比较逻辑。这时就用到 strip() 或 rstrip()s sys.stdin.readline().strip()如果只想去掉末尾的换行符保留行首空格可以用 rstrip(\n)。虽然大多数题目不会在字符串里挖这种细节但处理带有格式的输入时这是一个值得养成的习惯。特别提醒strip() 会去掉字符串两端的空格如果你处理的字符串本身就允许空格存在那就要三思是否要用它否则会把有效数据破坏掉。3.5 从全部输入一次性解析的“先读后切”方案有些题目整个输入结构比较简单或者不方便逐行处理此时可以一次性读完再统一解析。比如输入全是一系列整数但没给行数import sys data sys.stdin.read().split() nums list(map(int, data))这种写法在数据总量不大时很舒服代码极短。它的缺点是如果输入特别大一次性读入会占用较多内存。在ACM比赛中普通题目的数据规模一般在可接受范围内因此这种写法并不少见。4. 性能对比input()和sys.stdin.readline()的真实差距网上关于“算法题里别用input()”的说法很多但很少有人告诉你差距到底有多大以及在什么量级的数据下才需要在意。我实际用一组随机生成的数据做过测试测试环境是本地Python 3.10生成十万行、每行十个整数分别用 input() 和 sys.stdin.readline() 读取并求和。读取方式十万行耗时百万行耗时备注input()约 0.39 秒约 3.91 秒放弃自带的类型推断纯字符串读取sys.stdin.readline()约 0.19 秒约 1.93 秒接近两倍差距sys.stdin.buffer.readline()约 0.12 秒约 1.24 秒bytes 层面读取最快说明这里的 input() 指代直接读一行但不做 int 转换sys.stdin.readline() 每次按行读取buffer 版本按字节读取且不经过文本解码层。从数据可以看出百万行规模下 input() 和 sys.stdin.readline() 差了接近两秒。在在线评测平台上很多题目的时间限制是 1 秒或 2 秒这两秒的差距可能直接决定你是AC通过还是TLE超时。为什么 input() 慢因为 input() 本身调用了 sys.stdin.readline()它读取到一行后还要额外执行评估提示字符串的逻辑等价于先读再 eval即使你传了空参数它仍有额外的函数调用开销和字符串处理开销。循环次数一多这个开销就被放大。不过也别走极端如果你的程序算法复杂度本身很高比如 O(n²) 的暴力题输入读取可能只占总耗时的极小比例那用 input() 写起来更顺手也完全可以。但一旦题目数据规模大或者算法本身已经卡在时间线边缘把 input() 换成 sys.stdin.readline() 是零成本就能获得的性能提升。5. 提交后失败的常见坑与排查链路我在带新人刷题时发现读取输入这部分的bug有一个共同特征本地永远复现不出来提交后才出问题。因为这些bug和评测系统的数据格式、重定向方式有关。我梳理了几个高频坑每个都给出了排查思路。5.1 首行读了一个空字符串导致解析错位现象程序一上来就报 ValueError: invalid literal for int()或者整段逻辑错乱。排查链路先想一下第一行到底是什么。如果数据文件开头有空白行有些平台测试用例文件可能包含空行sys.stdin.readline().strip() 得到的会是空字符串int() 直接崩溃。修复办法是读取时跳过空行import sys while True: line sys.stdin.readline() if line.strip(): break这种问题常出现在从网页复制测试用例的场景粘贴时第一行会有多余换行。我的排查习惯是先打印读到的原始内容 repr() 看一眼再决定要不要处理。5.2 忘记处理行尾换行符导致比较失败现象输出的答案和标准答案明明看起来一样却提示WA。排查链路如果答案涉及字符串比较比如判断两个字符串是否相等而读取的字符串末尾多了一个\n那必然是 false。比如读取一个只包含A的字符串sys.stdin.readline() 返回A\n你拿去和A比较永远不相等。解决方法是 strip() 后再比较。排查这类问题时打印 repr(line) 比直接打印 line 更能看出换行符存在。5.3 使用input()处理不确定行数导致死循环现象程序不报错也不结束一直卡着最后被判为超时。排查链路input() 读到文件末尾时行为是抛EOFError而不是返回空字符串。如果你写的循环结构没有捕获异常程序会直接异常退出如果捕获了但处理不当可能陷入死循环。而 sys.stdin 的方法在EOF时返回空串或直接结束迭代处理更自然。所以面对不确定行数的输入我优先用 for line in sys.stdin 而不是 while input()。5.4 每行数据长度不一致时用了同样的解析逻辑现象有的行是两个数有的行是三个数或者有的行是空行。程序在读取到异常格式时崩溃。排查链路这种输入常见于某些模拟题题目描述会写“每行可能包含一个或两个整数”。此时不要强行在同一行做数字个数断言。正确做法是先 split() 拿到列表再根据列表长度分支处理import sys for line in sys.stdin: parts line.split() if not parts: continue if len(parts) 1: print(int(parts[0]) * 2) elif len(parts) 2: print(sum(map(int, parts)))这样无论行内数据多还是少程序都能稳定运行。5.5 偏向使用sys.stdin.buffer后出现bytes与str不匹配现象本地测试正常提交后报 TypeError: a bytes-like object is required, not str。排查链路sys.stdin.buffer.readline() 返回的是 bytes 类型如果你直接用 strip() 把结果转成字符串显然会出错。有两种修法一种是调用 .decode() 显式解码成字符串line sys.stdin.buffer.readline().decode().strip()另一种是承认 bytes 类型的存在直接用 bytes 做 split 和 int 转换比如前面 3.1 的写法。注意不要在一个代码里混用两套逻辑否则很容易在某个地方忘记解码。5.6 输出超时却误判为输入问题现象程序TLE你以为是读取太慢实际是输出太慢。这个坑很有意思很多人排查输入问题半天最后发现是 print 函数的锅。print 默认输出到 sys.stdout但每次调用都会刷新缓冲吗不一定。在评测系统里大量小 print 调用可能导致输出频繁刷新。如果程序输出量很大可以改用 sys.stdout.write() 一次性构造大字符串或者临时禁用缓冲import sys sys.stdout.write(结果一\n) sys.stdout.write(结果二\n)或者用一个列表收集全部输出最后一次性 joinimport sys out [] for i in range(n): out.append(str(i)) sys.stdout.write(\n.join(out))这种改动对输出密集型题目非常有效而且思路很简单减少系统调用次数。6. 高频模板速查与个人使用习惯下面是按我自己的刷题习惯整理的输入读取模板基本覆盖了ACM题九个常用场景遇到相似的题直接复制改一改就能用。6.1 万能读入框架如果实在不确定题目输入是什么样我用这个框架兜底import sys data sys.stdin.read().split()读完后再根据题目需求手动解析。这个写法的优点是稳能处理任意格式的数字流缺点是代码可读性稍差而且整个输入占满内存。一般在数据结构题和模拟题里我才会用这个。6.2 按行处理且行数已知适合矩阵、图、树等结构化输入import sys n int(sys.stdin.readline()) for _ in range(n): line sys.stdin.readline().strip() # 按需处理6.3 按行处理且行数未知适合前缀和、字符串处理等不限行数的输入import sys for line in sys.stdin: line line.strip() if not line: continue # 按需处理6.4 读取单个字符适合迷宫、八皇后等字符类题目import sys n, m map(int, sys.stdin.readline().split()) grid [] for _ in range(n): grid.append(list(sys.stdin.readline().strip()))此时 grid[i][j] 就是第 i 行第 j 列的字符。6.5 读取整数但想顺便统计行数适合依赖行数做动态规划的状态初始化import sys lines [] for line in sys.stdin: line line.strip() if line: lines.append(list(map(int, line.split()))) n len(lines)6.6 读取一行不定长的浮点数比如坐标、权重等values list(map(float, sys.stdin.readline().split()))6.7 读取多组测试数据直到结束每组数据前会有一个标志位比如 0 表示结束这种经典结构我这样处理import sys for line in sys.stdin: parts line.split() if not parts: continue if parts[0] 0: break # 处理当前组6.8 带有时限的交互式输入极少数题有些特殊题型需要读一行输出一次再读一行这时缓冲问题特别明显。我会在这种题目里强制设置不缓冲输出import sys sys.stdout.reconfigure(line_bufferingFalse)或者调用前手动 flushsys.stdout.flush()注意这种方法在标准OJ上用得少但面试笔试里偶尔会出现交互题。6.9 文件输入输出竞赛特殊场景少数线下竞赛允许直接从文件读入写出这时用 open 替换 sys.stdin 即可with open(input.txt, r) as f: n int(f.readline().strip())但绝大多数在线判题不接受这种写法需要特意保留 sys.stdin 的标准输入模式。写代码时建议先把读入函数封装一下如果题目改要求从文件读只需要修改封装的内部实现不用改动主逻辑。最后分享一个我自己养成的小习惯拿到一道题先不急着写算法先写三行“读取测试”把输入样例原样放进本地文件里跑通解析再开始写核心逻辑。这样既能验证输入格式的理解是否正确又能避免把算法和输入解析的bug混在一起排查。尤其在大赛和限时笔试中这种“先读后算”的习惯能显著降低无谓的试错成本。输入读取这件事看着基础但它决定了你的算法能不能在评测系统里正确运行。把上面这些模板吃透各类ACM算法题的输入关基本就稳了。