查看: 1184|回复: 3

Python输入输出避坑:print刷新、编码报错与文件读写

[复制链接]
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
Python的输入输出看起来简单,实际写脚本时最容易翻车。程序的数据来源是输入,数据去向是输出。键盘、文件、网络报文都是输入;屏幕日志、磁盘文件、网络请求都是输出。Python内建的 input() 和 print() 只是冰山一角,底层支撑它们的是三个标准流:stdin、stdout、stderr。

在 sys 模块中对应 sys.stdin、sys.stdout、sys.stderr。print() 可以理解为 sys.stdout.write() 的便捷封装,input() 类似 sys.stdin.readline()。理解这层关系后,重定向输出、替换输入源时就不会只盯着 print 和 input。
  1. import sys
  2. sys.stdout.write('你好,世界\n')
  3. # data = sys.stdin.readline()
复制代码

一、print 的参数与格式化
print 除了打印内容,还有 sep、end、file 三个常用参数。sep 控制多个值之间的分隔符,默认空格;end 控制结尾字符,默认换行符;file 指定输出目标,默认 sys.stdout。把内容写进文件时可以传 file 参数:
  1. print('2025', '01', '18', sep='-')
  2. print('下载中', end='')
  3. print('...', end='')
  4. print('完成')
  5. with open('log.txt', 'a', encoding='utf-8') as f:
  6.     print('这里是要记的日志', file=f)
复制代码

注意:print 写入的是字符串,如果文件以二进制模式打开,会直接报错。写日志建议用文本模式,或者直接用 f.write()。

格式化字符串有 % 占位、str.format()、f-string 三种。老教程常推 %,参数一多可读性差。f-string 从 Python 3.6 开始支持,可读性最高、性能最好,还能直接放表达式和函数调用。写爬虫拼接 URL、构造请求头、打印调试信息时尤其方便。
  1. name = '小明'
  2. score = 92.5
  3. print('姓名:%s,成绩:%.1f' % (name, score))
  4. print('姓名:{},成绩:{:.1f}'.format(name, score))
  5. print(f'姓名:{name},成绩:{score:.1f}')
复制代码

输出不刷新是常见坑。print 默认行缓冲,遇到换行符会刷新;但在循环里用 end='' 持续打印进度,或把输出重定向到管道时,缓冲区不一定立刻刷新。加 flush=True 可以强制刷新:
  1. import time
  2. for i in range(10):
  3.     print(f'进度:{i}/10', end='\r', flush=True)
  4.     time.sleep(0.5)
复制代码

\r 让光标回到行首,配合 flush=True 就能看到不断更新的进度条。也可以在运行脚本时加 python -u 强制无缓冲。

二、input 的细节与多行输入
input() 从 stdin 读一行,去掉末尾换行符,返回字符串。不管输入的是数字还是其他内容,返回值永远是字符串,直接做算术运算会报 TypeError。第一步永远是类型转换,整数用 int(),浮点数用 float()。用户输入不一定是合法数字,int('abc') 会抛 ValueError,实际项目必须做异常处理。
  1. age = input('请输入你的年龄:')
  2. print(age + 1)  # TypeError
  3. # 正确做法
  4. try:
  5.     age = int(input('请输入你的年龄:'))
  6.     print(age + 1)
  7. except ValueError:
  8.     print('请输入合法数字')
复制代码

多行输入有两种常见方式。行数未知时用 EOFError 判断结束:
  1. lines = []
  2. while True:
  3.     try:
  4.         line = input()
  5.         lines.append(line)
  6.     except EOFError:
  7.         break
复制代码

数据量大或用管道重定向输入时,可以直接读整个标准输入:
  1. import sys
  2. data = sys.stdin.read().strip().split()
复制代码

例如 python sum.py < numbers.txt,< 会把文件内容作为标准输入喂给程序,sys.stdin.read() 一次性读取全部内容再拆分处理。处理日志文件、批量文本时很实用。

输入不可信。文件读取、网络请求都可能返回非预期格式。写命令行计算器时,用户输入的是表达式字符串,至少要做一次合法性检查,再考虑交给 eval(),能不用尽量不用。输入侧每加一层防御,后面逻辑就少一次连锁崩溃。
  1. expr = input('请输入一个数学表达式:')
  2. if not all(ch in '0123456789+-*/(). ' for ch in expr):
  3.     print('表达式包含非法字符')
  4. else:
  5.     print(f'结果:{eval(expr)}')
复制代码

三、文件读写:open 模式与编码
文件读写是更常见的输入输出。open() 接收文件路径和模式两个核心参数。模式选错后果可能很严重:w 会清空原有内容;a 在文件末尾追加,不会覆盖已有内容;x 排他创建,文件存在则报错;r+ 读写但不截断文件,要注意文件指针位置;w+ 读写会截断文件;a+ 是追加模式,读的位置需要额外处理。往已有文件写内容时,优先用 a 追加模式。

编码是另一个大坑。Linux 和 macOS 上 Python 默认使用 UTF-8;Windows 上 open() 默认使用系统本地编码,中文系统通常是 GBK。在 Windows 下用默认参数读 UTF-8 文件,会出现乱码或 UnicodeDecodeError。爬虫把 UTF-8 网页内容写入本地文件时,如果不指定编码,可能抛 UnicodeEncodeError: 'gbk' codec can't encode character。解决办法是打开文件时显式指定 encoding='utf-8':
  1. with open('result.txt', 'w', encoding='utf-8') as f:
  2.     f.write(html_content)
复制代码

每次都写 encoding='utf-8' 有点啰嗦,但这是最稳妥的做法,代码移植到任何机器上行为都一致。

文件用完一定要关闭。忘记 f.close() 会导致句柄泄漏,程序跑久了报 Too many open files。with 语句在代码块执行完后自动关闭文件,即使内部抛异常也能正确关闭。同时读写多个文件可以用嵌套 with:
  1. with open('source.txt', 'r', encoding='utf-8') as src, \
  2.      open('target.txt', 'w', encoding='utf-8') as dst:
  3.     dst.write(src.read())
复制代码

爬虫结果落盘常用 JSON。json.dump 的 ensure_ascii=False 很重要,不加会把所有非 ASCII 字符转成 \uXXXX 转义序列,中文无法直接阅读:
  1. import json
  2. results = [
  3.     {'title': '文章1', 'url': 'https://example.com/1'},
  4.     {'title': '文章2', 'url': 'https://example.com/2'},
  5. ]
  6. with open('results.json', 'w', encoding='utf-8') as f:
  7.     json.dump(results, f, ensure_ascii=False, indent=2)
复制代码

读取大文件不要用 f.read() 一次性读进内存,几个 GB 的文件会直接爆内存。文件对象本身是迭代器,逐行读取既快又省内存:
  1. with open('big_log.txt', 'r', encoding='utf-8') as f:
  2.     for line in f:
  3.         # 处理每一行
  4.         pass
复制代码

四、常见 IO 问题排查
输出不显示:代码里写了 print('搞定了') 但屏幕没内容,通常是程序崩溃或缓冲区没刷新。正常退出时缓冲区会清空,被强杀或卡住时内容会丢失。临时解决可以 flush=True,或运行脚本时加 python -u。

编码报错 UnicodeEncodeError:三种解法。第一,文件读写时显式指定 encoding='utf-8';第二,修改标准输出流编码;第三,临时调试可设置环境变量 PYTHONIOENCODING=utf-8。修改标准输出编码的写法如下,但正式项目更建议在文件读写层把编码规范好:
  1. import sys
  2. import io
  3. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
复制代码

文件找不到或权限拒绝:FileNotFoundError 先检查路径,用 os.path.exists() 确认,再尝试绝对路径。Linux 下 PermissionError 多半是文件属主或权限位不对,先看目录能不能写,不要先怀疑代码。

输入卡住不动:脚本里用了 input(),在定时任务等无人值守场景中会永久阻塞。自动化脚本尽量避免 input(),改成从命令行参数或配置文件读取:
  1. import sys
  2. if len(sys.argv) > 1:
  3.     name = sys.argv[1]
  4. else:
  5.     name = '默认名称'
复制代码

快速排查表:
打印不刷新:加 flush=True 或 python -u。
中文乱码:用 print(sys.getdefaultencoding()) 查看默认编码,检查文件编码是否匹配。
写入文件后内容为空:检查是否使用 with 或忘记关闭文件。
FileNotFoundError:用 os.path.exists('路径') 检查。
读取大文件卡顿:改用 for line in f 逐行读取。

五、IO 对性能的影响
频繁 print 会拖慢程序。终端输出速度远低于 CPU 处理速度,循环里大量 print,程序大部分时间都在等 IO。调试时可以随意打印,正式运行时减少不必要输出,或用 logging 统一管理日志级别:
  1. import logging
  2. logging.basicConfig(level=logging.INFO, format='%(message)s')
  3. logger = logging.getLogger(__name__)
  4. logger.debug('这里是调试信息')
  5. logger.info('这里是正常输出')
复制代码

写文件时逐条写入会产生大量小 IO,效率很低。攒一批再写,或使用缓冲写入。写一万条数据,不要循环 f.write() 一万次,可以先把内容存到列表,最后用 writelines 一次性写入:
  1. lines = []
  2. for i in range(10000):
  3.     lines.append(f'第{i}行数据\n')
  4. with open('out.txt', 'w', encoding='utf-8') as f:
  5.     f.writelines(lines)
复制代码

批量写入后,写盘时间可能缩短几个数量级。

Linux 和 macOS 下,管道和重定向能把输入输出的威力放大。一个程序的输出可以作为另一个程序的输入:
  1. python fetch_data.py | python analyze.py
复制代码

fetch_data.py 往标准输出写数据,analyze.py 从标准输入读数据,两个程序通过管道衔接。理解了 stdin/stdout,就能理解这种脚本协作模式。

输入输出不是简单的“打印”和“接收”。它决定了程序能否与人顺畅交互、能否在复杂环境稳定运行、能否高效处理海量数据。遇到 IO 问题时,先想清楚程序在从哪条管道读数据、往哪条管道写数据,大部分问题都能定位。
回复

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: Python输入输出避坑:print刷新、编码报错与文件读写

楼主总结得挺全,尤其 print 刷新和文件编码这两个坑,真是写脚本时最容易忽略的。循环里用 end='' 打进度,本地终端看着正常,一重定向到管道或日志就可能半天不输出,加 flush=True 确实立刻见效;python -u 这个办法也很实用。编码方面,Windows 下不显式指定 encoding='utf-8',读 UTF-8 文件很容易乱码或直接报 UnicodeDecodeError,所以现在 open 写文件基本都会带上这个参数,虽然啰嗦但省心。 input 返回字符串这个点也很有共鸣,直接拿输入做算术会报 TypeError,必须先转换并处理 ValueError。多行输入用 EOFError 判断结束、数据量大时用 sys.stdin.read() 配合重定向,处理批量文本确实方便。文件模式里 w 和 a 的区别也要特别小心,一不留神就把原内容清空了,能用 with 自动关闭就尽量用,避免句柄泄漏。感谢分享,这些细节很值得放到日常脚本模板里。
回复 支持 反对

使用道具 举报

发表于 昨天 19:10 | 显示全部楼层

Re: Python输入输出避坑:print刷新、编码报错与文件读写

感谢分享,这些坑总结得很实在。平时写脚本最常遇到的就是 print 不刷新和编码问题:循环里用 end='' 输出进度时,不加 flush=True 经常看着像卡住;Windows 下不显式指定 encoding='utf-8',读文件或写网页内容特别容易乱码、报 UnicodeDecodeError 或 UnicodeEncodeError。文件操作我也越来越习惯用 with,省得忘了 close 导致句柄泄漏。输入这块确实不能想当然,input 拿到永远是字符串,类型转换和异常处理必须做,多行输入用 EOFError 或 sys.stdin.read() 都很实用。f-string 现在也是首选,拼接 URL、打日志都清楚。感谢整理,回头把 -u 和 file 参数写日志这些细节再巩固一下。
回复 支持 反对

使用道具 举报

发表于 昨天 19:20 | 显示全部楼层

Re: Python输入输出避坑:print刷新、编码报错与文件读写

楼主这篇把常见坑基本都点到了。print 的 flush 我以前也没太在意,循环里用 end='' 打进度条时总觉得不动,加 flush=True 或者跑脚本时用 python -u 才正常。input 永远返回字符串这点也特别容易忘,直接做运算就 TypeError,所以先转换再做 try except ValueError 很有必要。文件读写里 encoding='utf-8' 我现在也是能写就写,尤其在 Windows 上被 GBK 坑过之后,统一指定确实省心。with 自动关闭文件也很关键,不然句柄泄漏到后面很麻烦。感谢分享,很实用。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-22 06:08 , Processed in 0.021623 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部