2人参与 • 2026-09-21 • Python
写python这几年,我越来越觉得“输入输出”是程序里最容易被忽视,却又最容易出问题的地方。很多人一上来就追着爬虫、算法、第三方库跑,结果在项目里写脚本时,一个 input 卡住、一个编码报错、一个输出没刷新,就能耗掉一下午。如果把程序比作一个人的话,输入输出就是它的“耳朵”和“嘴巴”,代码里所有的逻辑、计算、数据处理,最终都要靠这两样东西和外部世界打交道。这篇文章我想把python的输入输出彻底讲透,从基础的 print 和 input ,到标准流、格式化输出、文件读写,再到我这些年踩过的各种io坑,一次性说清楚。
这篇文章适合谁?如果你刚开始学python,想知道 print 和 input 到底怎么用、背后发生了什么;如果你写过一阵子脚本,但总在编码、缓冲区、文件读写这些地方翻车;又或者你想系统地梳理一下输入输出这块,以便日后写爬虫、自动化脚本、数据处理时少走弯路——那这篇文章就是冲着你来的。我尽量不堆砌文档式的内容,而是把我实际写代码时的思路、取舍和教训都讲出来,咱们像同事聊代码一样,把输入输出这层窗户纸捅破。
你写任何程序,本质上都是在处理数据。数据的来源是输入,数据的去向是输出。键盘敲进去的字符、鼠标点击的坐标、文件里的内容、网络接口返回的报文,这些都是输入;屏幕上打印的日志、写入磁盘的数据、发送到别处的请求,这些都是输出。python最常用的两个内建函数 input() 和 print() ,其实只是输入输出体系里的冰山一角。真正在底层支撑它们的,是三个标准流: stdin (标准输入)、 stdout (标准输出)、 stderr (标准错误)。
你可以把这三个流理解成程序的三条管道。正常情况下, input() 从 stdin 这条管道里读数据, print() 往 stdout 这条管道里写数据,而 stderr 是专门用来输出错误信息的。为什么要单独搞一个 stderr ?因为在实际生产环境里,你往往想区分“正常输出”和“错误输出”——比如在linux下跑一个脚本,你可以把正常日志重定向到文件a,把错误日志重定向到文件b,互不干扰。这在排查问题的时候特别有用。
python的 sys 模块里,直接对应着这三个流: sys.stdin 、 sys.stdout 、 sys.stderr 。有意思的是, print() 和 input() 虽然是你最常用的,但它们不过是 sys.stdout.write() 和 sys.stdin.readline() 的“便捷封装”。也就是说,底层真正干活的其实是这三个流对象。
import sys
# print其实等价于往stdout里写一行
sys.stdout.write("你好,世界\n")
# input其实等价于从stdin里读一行
# data = sys.stdin.readline()
很多人在代码里见过 sys.stdout ,但平时真正常用的还是 print 。不过理解这层关系有一个实实在在的好处:当你需要把输出重定向到文件、或者从字符串里读取输入时,你会懂得去操作 sys.stdout 和 sys.stdin ,而不是傻傻地在 print 和 input 上钻牛角尖。比如写测试用例的时候,你不想让程序真的弹出来等键盘,直接把 sys.stdin 替换成一个字符串流就行。
我之所以喜欢用“嘴巴”和“耳朵”这个比喻,是因为输入输出和人的交流方式实在太像了。说话是输出,听是输入。你说的话别人能不能听懂,取决于你的表达能力(输出格式);你听别人说话能不能听准,取决于你的理解能力(输入解析)。在实际写代码时也一样—— print 的数据格式不对,别人(或者其他程序)看不懂; input 读进来的数据没处理好,后面算得再准也是白搭。
先说说 print 本身。很多新手以为 print 就是把括号里的东西打出来完事,其实它有几个隐藏参数,用好了能让输出灵活很多。 sep 是分隔符,默认是一个空格,比如 print(1, 2, 3) 会输出“1 2 3”。 end 是结尾字符,默认是换行符 \n ,如果你不想换行,可以用 end="" 。 file 是输出目标,默认是 sys.stdout ,但你可以把它指向一个文件对象,实现“把内容写到文件里”。
# 用sep把多个值拼成一行
print("2025", "01", "18", sep="-")
# 用end实现不换行输出
print("下载中", end="")
print("...", end="")
print("完成")
# 用file把输出重定向到文件
with open("log.txt", "a", encoding="utf-8") as f:
print("这里是要记的日志", file=f)
这里有个我早年间踩过的坑:用 print 往文件里写东西时,如果文件是以二进制模式打开的,会直接报错。因为 print 写的是字符串,二进制模式需要字节串。所以如果要往文件里写日志,建议用文本模式打开,或者直接用 f.write() 。
输出数据时,最核心的操作就是格式化字符串。python提供了三种方式: % 格式占位、 str.format() 方法、f-string(格式化字符串字面量)。我见过很多老教程还在推 % 占位,那是c语言时代留下的习惯,可读性差,尤其当参数变多时,你会迷失在一堆 %s 和 %d 里。
name = "小明"
score = 92.5
# 老式%占位
print("姓名:%s,成绩:%.1f" % (name, score))
# str.format()
print("姓名:{},成绩:{:.1f}".format(name, score))
# f-string(python 3.6+)
print(f"姓名:{name},成绩:{score:.1f}")
我强烈推荐f-string。原因很简单:可读性最高、性能最好、写起来最自然。你在f-string里可以直接放表达式,甚至调用函数,比如 f"结果:{calculate(x):.2f}" 。在写爬虫的时候,拼接url、构造请求头、打印调试信息,f-string能省下不少事。
这里有一个特别常见的坑——输出不刷新。python的 print 默认是带行缓冲的,意思是说,只要遇到换行符,它就会把缓冲区里的内容刷到屏幕上。但如果你在循环里用 end="" 持续打印进度,或者把输出重定向到管道(比如用 python script.py | tee log.txt ),缓冲区就不一定立刻刷新了,你会发现屏幕上半天没动静,其实程序在埋头干活。
解决办法很简单,加 flush=true 参数。
import time
for i in range(10):
print(f"进度:{i}/10", end="\r", flush=true)
time.sleep(0.5)
上面这段代码做一个简易的进度条, \r 是回车符,让光标回到行首,再配合 flush=true 强制刷新,就能看到一个不断更新的进度条。我在写爬虫脚本时,喜欢用这种方式打印每个页面的抓取状态,直观又不刷屏。
input() 这个函数,很多人只会在练习时用。它做的事情其实很简单:从 stdin 里读一行内容,去掉末尾的换行符,然后以字符串的形式返回。注意,不管你输入的是数字还是别的,它返回的永远是一个字符串。所以写 num = input("请输入数字:") 之后,如果你直接拿 num 去做算术运算,肯定会报错,因为类型不对。
age = input("请输入你的年龄:")
# 这里拿到的是字符串"18",不是数字18
print(age + 1) # typeerror: can only concatenate str (not "int") to str
所以处理输入时,第一步永远是类型转换。转成整数用 int() ,转成浮点数用 float() 。但这又引出一个新问题:用户输入的不一定是合法数字,万一输了个“abc”, int("abc") 会直接抛出 valueerror 。在实际项目中,输入是不可信的,你必须做好异常处理。
在竞赛刷题或数据处理场景里,经常需要读多行输入。如果行数已知,直接循环 input() 即可;如果行数未知,要在读到文件结尾时结束,那就得靠 eoferror 来检测。 input() 在读不到内容时会抛出 eoferror 异常,你可以用 try-except 捕获它。
lines = []
while true:
try:
line = input()
lines.append(line)
except eoferror:
break
另一种方式是直接读取整个标准输入,用 sys.stdin.read() 拿到所有内容,再按行分割。这种方式在数据量大、用管道重定向输入时特别高效。
import sys data = sys.stdin.read().strip().split()
比如你写一个脚本: python sum.py < numbers.txt , < 符号会把 numbers.txt 的内容作为标准输入喂给程序。此时程序里用 sys.stdin.read() 就能一次性读到文件全部内容,再拆分成数字列表进行求和。这个技巧在处理日志文件、批量文本时非常实用。
前面提到,输入是不可信的。这不只是用户的锅,文件读取、网络请求都有可能返回非预期格式。我写代码时有个习惯:凡是从外部拿数据,都要做一次“输入验证”。比如写一个命令行计算器,用户输入的是一个数学表达式字符串,你得先做合法性校验,再交给 eval() ,否则恶意输入可能会有风险。
expr = input("请输入一个数学表达式:")
# 别直接eval!先做简单检查
if not all(ch in "0123456789+-*/(). " for ch in expr):
print("表达式包含非法字符")
else:
print(f"结果:{eval(expr)}")
注意,我这里只是做了一个演示,实际开发中 eval 要非常谨慎地使用,能不用尽量不用。这个例子想说明的核心是:输入侧的每一层防御,都能避免后面逻辑的连锁崩溃。
聊完标准输入输出,我们得说说文件读写,因为从广义上看,这也是输入输出的一部分,而且在实际项目中比 input 和 print 用得更频繁。 open() 函数是文件读写的入口,它接收文件路径和模式两个核心参数。模式看着简单,但选错模式的后果可能很严重。
| 模式 | 作用 | 注意点 |
|---|---|---|
r | 只读,文件不存在则报错 | 最常用,安全 |
w | 写入,会清空原有内容 | 文件不存在则创建,但会覆盖旧数据 |
a | 追加,在文件末尾写入 | 不会覆盖已有内容 |
x | 排他创建,文件存在则报错 | 避免误覆盖 |
r+ | 读写,但不会截断文件 | 注意文件指针位置 |
w+ | 读写,会截断文件 | 慎用 |
a+ | 读写,追加模式 | 读的位置需要额外处理 |
我在早期犯过一个低级错误:想往一个已有数据的配置文件里加几行,结果用了 w 模式,运行完整个文件被清空了。从那以后,只要涉及“往已有文件里写东西”,我第一反应就是用 a 追加模式。
文件读写的另一个大坑是编码。不同系统默认编码不一样:在linux和macos上,python默认使用utf-8;在windows上,python的 open() 函数默认编码是系统的本地编码(比如中文系统是 gbk )。当你在windows下用默认参数读取一个utf-8编码的文件时,就会出现乱码,或者直接抛 unicodedecodeerror 。
我写爬虫脚本的时候,经常要处理网页内容再写入本地文件。网页内容是utf-8,本地系统是gbk,如果写入时不指定编码,程序就会抛 unicodeencodeerror: 'gbk' codec can't encode character 。解决方案很简单,打开文件时显式指定 encoding="utf-8" 。
with open("result.txt", "w", encoding="utf-8") as f:
f.write(html_content)
每次 open 都写 encoding="utf-8" 确实有点啰嗦,但这是最稳妥的做法。你还可以用 io.open 或者设置环境变量来修改默认编码,不过我认为最清晰的方案就是显式传入参数,这样代码移植到任何机器上行为都是一致的。
文件打开之后,一定要记得关闭。很多人初学时会写 f = open(...) ,用完了忘了 f.close() ,导致文件句柄泄漏,程序跑久了就会报“too many open files”。更稳妥的写法是使用 with 语句,它会在代码块执行完后自动关闭文件,即使在代码块内部抛了异常,文件也会被正确关闭。
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# 出了with块,文件自动关闭
如果你需要同时读写多个文件,也可以用嵌套的 with :
with open("source.txt", "r", encoding="utf-8") as src, \
open("target.txt", "w", encoding="utf-8") as dst:
dst.write(src.read())
这段代码把一个文件的内容整个复制到另一个文件。虽然看起来简单,但它背后隐藏着关于输入输出流的精髓:数据从源文件(输入端)流向目标文件(输出端),中间没有任何打印和交互,却完成了一次完整的“对话”。
结合搜到的热词“python爬虫”,我再多说一点。写爬虫时,你实际上是在做一道复杂的输入输出题:网页内容是外部输入,本地文件是输出。爬取结果的落盘,最常用的就是json格式,因为结构清晰、跨平台兼容好。
import json
results = [
{"title": "文章1", "url": "https://example.com/1"},
{"title": "文章2", "url": "https://example.com/2"},
]
with open("results.json", "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=false, indent=2)
这里有个细节: ensure_ascii=false 参数很重要。如果不加,默认情况下 json.dump 会把所有非ascii字符转成 \uxxxx 形式的转义序列,文件里存的就是一堆反斜杠编码,人类无法阅读。加上 ensure_ascii=false 后,中文会以原始字符形式保存,打开文件一目了然。
典型案例:代码里写了 print("搞定了") ,但程序运行完,屏幕上什么都没有。这种情况大概率是程序崩溃或者缓冲区没刷新。如果程序正常退出,缓冲区会被清空;但如果程序被强杀或者卡住了,缓冲区里的内容就丢了。解决办法是一直提到的 flush=true ,或者在脚本开头用 python -u 参数强制无缓冲运行。
这个错误我在windows下见的次数最多。解决思路有三个:第一,像前面说的,写入文件时显式指定 encoding="utf-8" ;第二,修改标准输出流的编码;第三,如果只是临时调试,可以设置环境变量 pythonioencoding=utf-8 。
import sys import io # 动态修改标准输出的编码(某些windows环境下) sys.stdout = io.textiowrapper(sys.stdout.buffer, encoding="utf-8")
不过我要提醒一句,这种全局替换输出的写法在日常脚本里够用,但在正式项目里还是建议在文件读写这层就把编码问题规范好,不要回到终端里搞骚操作。
爬虫或者自动化脚本经常要读取配置文件,如果文件路径写错了,会抛 filenotfounderror 。排查思路很简单:先用 os.path.exists() 确认路径是否存在,再用绝对路径试一次。权限问题也很常见,linux下 permissionerror 大概率是文件属主或权限位不对。这个时候不要先怀疑代码,先看看目录能不能写。
如果代码里用了 input() ,在自动化任务中运行(比如定时任务)时,程序会因为等待键盘输入而永久阻塞。这是一个很隐蔽的坑:你的程序明明没写错,但它在无人值守的服务器上卡死了。解决办法是:在需要自动化运行的脚本里,尽量避免使用 input() ,改成从命令行参数或配置文件里读取输入。
import sys
# 推荐:从命令行参数接收输入
if len(sys.argv) > 1:
name = sys.argv[1]
else:
name = "默认名称"
| 现象 | 可能原因 | 快速排查命令/代码 |
|---|---|---|
| 打印不刷新 | 缓冲问题 | 加 flush=true 或 python -u |
| 中文乱码 | 编码不匹配 | print(sys.getdefaultencoding()) |
| 写入文件后内容为空 | 忘记关闭文件 | 检查是否使用 with |
报 filenotfounderror | 路径错误 | os.path.exists("路径") |
| 读取大文件卡顿 | 一次 read() 全部 | 改用 for line in f: 逐行读取 |
关于“读取大文件”,我再补充一个经验。有些人习惯用 f.read() 把整个文件读进内存,如果文件上了几个gb,内存直接爆掉。正确做法是逐行读取:
with open("big_log.txt", "r", encoding="utf-8") as f:
for line in f:
# 处理每一行
pass
文件对象本身是一个迭代器,底层有缓冲机制,逐行读既快又省内存,这是处理大文件的标准姿势。
写了一段时间脚本之后,你会发现io操作往往是性能瓶颈。cpu计算再快,如果每一步都要往终端打印,速度也会被拖慢。原因很简单:终端输出的速度远低于cpu处理速度。在循环里频繁 print ,程序大部分时间都花在等待io上。
解决思路:调试时随意打印,但正式运行时减少不必要的输出,或者把日志级别提高。如果你的代码里有大量 print 需要清理,可以用一个日志对象来统一管理。
import logging
logging.basicconfig(level=logging.info, format="%(message)s")
logger = logging.getlogger(__name__)
# 调试时用debug级别,生产环境设置为info
logger.debug("这里是调试信息")
logger.info("这里是正常输出")
写文件时,逐条写入会产生大量小io操作,效率很低。正确做法是攒一批再写,或者使用缓冲写入。举个例子,写一万条数据,最好不要在循环里 f.write() 一万次,而是先把内容存到一个列表里,最后一次性 join 再写入。
lines = []
for i in range(10000):
lines.append(f"第{i}行数据\n")
with open("out.txt", "w", encoding="utf-8") as f:
f.writelines(lines)
这样改完,写文件的时间能缩短几个数量级。我遇到过把爬虫数据写盘时,原来一条条写要跑好几分钟,改成批量写后几十秒搞定。
在linux或macos下,输入输出的威力在命令行里展现得淋漓尽致。你可以用管道把一个程序的输出喂给另一个程序的输入,这就是所谓的“组合拳”。比如:
python fetch_data.py | python analyze.py
fetch_data.py 往标准输出写数据, analyze.py 从标准输入读数据,两个程序通过管道无缝衔接。理解了标准输入输出,你就理解了这个模式有多强大。python的 print 负责“说”, input 或 sys.stdin 负责“听”,在脚本世界里这也是程序间协作的通用语言。
我见过不少人学python,觉得输入输出太简单,一眼就跳过。但真到了写爬虫、写自动化脚本、处理数据的时候,各种io相关的坑一个接一个冒出来。这一篇就是把我这些年积累的经验、踩过的坑、压箱底的小技巧都倒出来,从 print 和 input 这两个最常见的入口说起,一路延伸到标准流、格式化、文件读写、编码处理和性能优化。
我个人在实际操作中最深的体会是:输入输出绝不只是“把数据打出来、收进来”这么简单,它决定了你的程序能不能和人顺畅交互、能不能在复杂环境里稳定运行、能不能高效处理海量数据。每当你觉得一个io问题莫名其妙的时候,先静下来想想底层发生了什么——你的程序到底在从哪条管道里听数据,又在往哪条管道里说话?想通了这层,大部分问题都迎刃而解。
到此这篇关于python中输入输出全指南:从print/input到文件读写与io避坑的文章就介绍到这了,更多相关python输入输出内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论