news 2026/10/14 5:42:38

Python输入输出全解析:从input/print到文件读写与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python输入输出全解析:从input/print到文件读写与实战

刚把这节课的资料过了一遍,正好讲到Python的输入输出。很多初学者会觉得这章太简单——不就是input()和print()嘛,有什么好讲的?但说句实话,我在带模拟项目X的开发时,见过太多因为输入输出细节没处理好而翻车的情况:要么是读文件时编码崩了,要么是用户输了个非数字导致程序直接退出,要么是写日志时缓冲区没刷新导致排障信息丢失。这章内容看似基础,却是几乎所有程序交互的地基,把细节啃透,后面写爬虫、写命令行工具、做数据处理时都会轻松很多。

这讲适合三类人:刚学完变量和数据类型、准备接触交互式程序的新手;写过一阵子Python但总觉得输入输出“差点意思”的人;以及想系统性查漏补缺、把脚本写得更健壮的开发者。我会把input()和print()的底层机制、格式化输出的几种姿势、文件读写的最佳实践一次讲清楚,最后再用一个实操案例把知识点串起来。

1. 输入这件事,比你想的更讲究

1.1 input() 的本质:它到底返回了什么

先记住一句话:input()永远返回字符串。不管用户在终端里敲的是数字、是字母、是空格,它统统当成字符串处理。这一点怎么强调都不过分,因为初学者踩的坑十个里有八个来自类型问题。

age = input("请输入你的年龄: ") print(type(age)) # <class 'str'> print(age + 1) # 报错:TypeError: can only concatenate str (not "int") to str

看到没?age + 1直接炸了,因为age是字符串,不能和整数相加。正确姿势是先转换类型:

age = int(input("请输入你的年龄: ")) print(f"明年你{age + 1}岁")

这里有个生活化的类比:input()就像你去窗口取快递,窗口工作人员不管包裹里装的是书还是衣服,他递给你的永远是那个快递盒——字符串就是那个快递盒,你非得自己拆开(类型转换),才能拿到里面的真正数据(数字、布尔值等)。

需要注意的是,int()转换不是万能的。用户要是输入了"abc",int()会直接抛出ValueError;输入了"12.5",int()同样会报错,得用float()。这就是为什么健壮的程序要做异常处理,后面第2部分会展开讲。

1.2 一次读入多个值:split() 的妙用

很多时候你想让用户一次输入多个值,比如“输入两个数字,空格隔开”。新手常犯的错误是连着写两次input(),非要用户按两次回车。更好的做法是一行读入,然后用split()拆分:

a, b = input("请输入两个数字,用空格隔开: ").split() a = float(a) b = float(b) print(f"两数之和: {a + b}")

如果用户输入"3.5 7.2",split()会按空白字符拆出['3.5', '7.2'],然后解包给a和b。这就排上用场了:写算法题、写竞速脚本时,一行读入多个数据是标准操作。

顺便提一句,split()也可以指定分隔符。比如从配置格式"name:age:score"里解析数据:

data = "Alice:20:95.5" name, age, score = data.split(":")

1.3 终极武器:用 sys.stdin 做批量输入

input()更准确的称呼是“每次读一行标准输入”。当你需要一次性处理成千上万行输入时(比如从文件重定向:python script.py < data.txt),逐行调用input()不是不行,但性能上会略逊于直接遍历sys.stdin。

import sys for line in sys.stdin: line = line.strip() if not line: continue parts = line.split(",") # 处理每一行逻辑...

这种写法在数据竞赛、算法批处理里非常好用,也是为什么很多在线判题系统的样例代码都用这种模式的原因。sys.stdin本身就是个文件对象,逐行迭代、缓冲读取,处理大输入时又快又稳。

注意:input()调用的底层就是sys.stdin.readline(),它会把末尾的换行符\n去掉再返回;而sys.stdin.readline()不会自动去换行符,你得自己strip()一下。

2. 输出不是 print 那么简单

2.1 print() 的隐藏参数

print()可能是大家学Python后写的第一个函数,但它的隐藏参数你未必全了解。完整签名如下:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
  • sep:多个对象之间的间隔符,默认是一个空格
  • end:行尾追加的字符串,默认是换行符\n
  • file:输出目标,默认是标准输出(屏幕),也可以指向文件对象
  • flush:是否立即刷新缓冲区,默认不刷新

sep和end用得很多。比如你想把列表里的元素用逗号拼成一行:

print(*["Java", "Python", "Go"], sep=", ") # 输出:Java, Python, Go

end最经典的用法是进度条或提示语句不换行:

import time for i in range(5): print(f"进度: {i + 1}/5", end="\r") time.sleep(1)

2.2 三种格式化输出方式对比

Python里格式化字符串主要有三种方式:老牌%占位符、str.format()、以及现代推荐的 f-string。直接上对照表:

方式示例适用场景可读性
%占位符"%s is %d years old" % (name, age)兼容老代码、日志模块较差
str.format()"{} is {} years old".format(name, age)模板复用、动态拼接中等
f-stringf"{name} is {age} years old"日常开发首选最佳

我现在的项目里几乎只用 f-string,它好在哪?看几个细节:

name = "小明" score = 72.5 # 保留两位小数、右对齐补零 print(f"{score:06.2f}") # 072.50 # 左对齐,宽度10,用*填充 print(f"{name:*<10}") # 小明******** # 千分位分隔 amount = 1234567 print(f"{amount:,}") # 1,234,567 # 百分比显示 rate = 0.8734 print(f"{rate:.2%}") # 87.34%

再说一个调试时特别香的技巧:f-string 里可以直接打印“变量名=值”:

user = "admin" status = "online" print(f"{user=} {status=}") # 输出:user='admin' status='online'

这比print("user:", user, "status:", status)清晰太多,我实际排障时经常靠它快速输出一批中间变量。

2.3 缓冲区这关不过,你连Bug都排不明白

很多人写Python遇到过一个诡异现象:程序里print()有输出,但屏幕上看不到,要等程序退出才一下子全冒出来。这就是标准输出缓冲区在作祟。

Python 的print()默认是行缓冲或块缓冲(取决于输出目标)。当输出到终端时,通常是行缓冲——遇到换行符就刷新;但程序异常终止或长时间运行却不换行时,缓冲内容可能来不及显示。

解决手段有三个:

  1. print(..., flush=True),强制立即刷新
  2. 在启动脚本时加python -u script.py,取消缓冲
  3. 设置环境变量:PYTHONUNBUFFERED=1

实际写长时间运行的脚本(比如每隔几秒打印一次进度,或写日志探针)时,我习惯直接把flush=True带上,省得排查时数据“卡在管道里”。

3. 不只是控制台:文件输入输出的实战能力

3.1 open() 用不好,数据全白费

文件读写是输入输出模块里最有“工程味”的部分。先看最常用的open()模式:

模式含义文件不存在时
r只读报错FileNotFoundError
w写入(覆盖)新建
a追加新建
r+读写报错
w+读写(覆盖)新建
a+追加并支持读新建

w和a的区别值得单独讲。w会清空原文件全部内容再写入,如果是日志系统每次启动都重置文件,那不是你想要的;a则是在文件末尾追加,适合记录持续产生的数据。

我见过一个真实事故:某同学写数据采集脚本,用w模式每天保存一次结果,结果某天程序重启,之前累计半天的数据全部被清空了。这就是没分清“覆盖”和“追加”的代价。

3.2 with 语句:你永远不该裸用 open()

正确的文件操作写法是用with上下文管理器:

with open("data.txt", "r", encoding="utf-8") as f: content = f.read()

好处有三层:with块结束后文件自动关闭,就算中途抛出异常也能正确释放资源;代码缩进清晰,明确表达“这个文件的存活范围只在块内”;你不需要手动记着f.close()。记住一句话:不在with里操作文件,就等于开车不系安全带,大多数时候没事,但出一次事故就够你受的。

读取文件有几种姿势,按需选择:

# 1. 一次性读全部(小文件) content = f.read() # 2. 读一行 line = f.readline() # 3. 读所有行到列表 lines = f.readlines() # 4. 逐行迭代(大文件推荐,省内存) for line in f: process(line)

处理上G的日志文件时,read()一次读入内存可能会直接把你机器拖垮,正确姿势是第四种逐行迭代,或者用带缓冲的for line in f流式处理。

3.3 编码问题:utf-8 是标配,写错就是乱码

如果你在 Windows 上打开一个用记事本保存的文本,再用 Python 读取,常常会看到UnicodeDecodeError或者满屏乱码。这就是编码不一致的问题。

统一规范如下:

  • 写代码时,所有文件读写都显式指定encoding="utf-8"
  • 不要依赖系统默认编码(Windows 下是gbk,Linux 下是utf-8,表现完全不同)
  • 读别人给的二进制文件时,先确认来源编码,不确定就先chardet或magic之类工具检测

踩坑案例很典型:某同学在 Windows 上写了一个脚本,把中文数据保存到CSV里,没有指定编码;脚本拿到 Linux 服务器上跑,输出的中文全乱了。后来统一改成open("output.csv", "w", encoding="utf-8-sig")才解决。注意这里用utf-8-sig而不是utf-8,是因为 Excel 打开 CSV 时需要 BOM(字节顺序标记),否则中文表头会乱。

3.4 不只是读写:指针推拉与文件定位

文件对象内部维护了一个“指针”,记录当前读写到什么位置。seek()和tell()就是控制这个指针的工具:

with open("data.txt", "r", encoding="utf-8") as f: print(f.tell()) # 0,起点 line = f.readline() print(f.tell()) # 读了多少字节,指针就在哪 f.seek(0) # 回到开头

在处理超大文件、需要跳跃读取的场景(比如二分查找某行数据)时,seek()必不可少。另外,二进制文件的读写也依赖这块:open("img.jpg", "rb")得到的是字节流,读出来是bytes类型。

4. 综合实战:写一个能用的命令行记账本

说了这么多原理,是时候把它们拼起来了。这里做一个模拟项目X:一个极简的文本记账本ledger.py,支持用户输入收支、保存到文件、读取展示。这个案例会串联起 input 类型转换、格式化输出、文件追加、异常处理多个知识点。

先画个功能清单:用户选择操作(1记账 / 2查看 / 3退出);记账时输入金额、分类、备注;金额用float()转换,非法输入要捕获;保存到records.txt,追加写入;查看时逐行读取并格式化打印。

完整代码如下:

import sys from datetime import datetime FILE_PATH = "records.txt" def add_record(): try: amount = float(input("金额: ")) except ValueError: print("金额必须是数字,操作取消") return category = input("分类: ").strip() or "未分类" note = input("备注: ").strip() now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") line = f"{now}|{category}|{amount:.2f}|{note}\n" with open(FILE_PATH, "a", encoding="utf-8") as f: f.write(line) print(f"已记录: {category} {amount:.2f} 元") def show_records(): try: with open(FILE_PATH, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue time_str, category, amount_str, note = line.split("|", 3) print(f"{time_str} | {category} | {float(amount_str):>8.2f} | {note}") except FileNotFoundError: print("还没有任何记录,先记一笔吧") def main(): actions = {"1": add_record, "2": show_records} while True: print("\n1. 记账") print("2. 查看记录") print("3. 退出") choice = input("请选择: ").strip() if choice == "3": print("再见") break action = actions.get(choice) if action: action() else: print("无效选择,请重新输入") if __name__ == "__main__": main()

这里有几个设计细节值得说道说道。

第一,add_record()里用float(input(...))并包了try/except,用户输入"abc"时程序不会崩溃,而是提示并返回,这就是健壮性。实际生产环境里,输入校验永远是第一道防线。

第二,文件用"a"模式追加,不会覆盖历史记录;写入的是一行带有分隔符|的纯文本,简单、可读、即使用记事本打开也能看懂。

第三,读取时用line.split("|", 3),3限制了最大分割次数,这样备注里就算有|字符,也不会把字段拆坏。

第四,主循环用字典映射操作函数,而不是一堆if/elif。这算是Python里一个很常见的“表驱动”思想,增加新功能时只需要在actions里加一项,主循环不用动。

运行效果大致是这样:

1. 记账 2. 查看记录 3. 退出 请选择: 1 金额: 35.5 分类: 餐饮 备注: 午饭 已记录: 餐饮 35.50 元

试想一下,如果没有with管理文件,如果没有捕获FileNotFoundError,这段代码在第一次运行“查看记录”时就会直接报错退出。这些细节就是“能跑的代码”和“好用的程序”之间的差距。

5. 常见问题与排查技巧实录

5.1 输入了非数字导致程序直接崩了

错误表现:ValueError: invalid literal for int() with base 10: 'abc'。

排查思路:这是类型转换失败。不要想着“让用户规规矩矩输入”,而是主动处理异常,或者用str.isdigit()先做校验。更稳妥的方式是写一个获取合法输入的小函数:

def safe_float_input(prompt): while True: raw = input(prompt).strip() try: return float(raw) except ValueError: print("输入不合法,请输入数字")

5.2 输出被“吞掉”或者顺序错乱

错误表现:多个print()的内容没有按预期顺序输出,或者长时间运行的程序中途看不到日志。

排查思路:八成是标准输出缓冲。调试期直接把输出的目标切到文件file=open("log.txt", "w")或者加上flush=True。有时候多线程环境下,print 的输出顺序也会乱,那是因为多个线程同时在往 stdout 写,应该统一由单线程或日志模块来负责输出。

5.3 读文件时报 UnicodeDecodeError

错误表现:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 0: invalid start byte

排查思路:源文件的编码不是 UTF-8。常见场景是在 Windows 上生成的文件用 GBK 编码保存。解决方式分两步:先确认文件格式(用编辑器看右下角编码,或用file命令),再在open()里指定正确的编码。如果确定是 GBK 系列,就写encoding="gbk";如果希望在 Windows 和 Linux 之间来回搬运都稳定,保存时另存为 UTF-8(Excel 场景用utf-8-sig)最省心。

5.4 文件读取卡死或内存暴涨

错误表现:程序读完某个大文件后内存占用飙升,甚至被系统杀掉。

排查思路:查看是不是用了f.read()把整个文件一次性读入。处理大文件时要逐行迭代:for line in f:而不是lines = f.readlines()。必要的话,配合itertools.islice()做分段读取,一次处理固定行数。

5.5 Windows 环境下换行符的坑

Windows 的换行是\r\n,Linux/macOS 是\n。如果你在 Windows 上写文本文件,默认会把\n转换成\r\n写入,这在 Python 的文本模式下是自动发生的。

但有一个情况要注意:你在 Windows 上读取一个 Linux 生成的\n文件,没问题;在 Linux 上读取 Windows 的\r\n文件,每一行结尾会多个\r,打印时容易看到奇怪的换行。解决办法是读取时用newline=""参数控制换行转换行为,或者处理前统一strip()把\r去掉。

with open("data.txt", "r", encoding="utf-8", newline="") as f: for line in f: line = line.rstrip("\r\n")

5.6 input() 里想显示默认值

有时候你想在提示中显示一个默认值,用户直接回车就算选了默认项。input()本身不直接支持默认值,但可以自己写:

def input_with_default(prompt, default=""): raw = input(f"{prompt} [{default}]: ").strip() return raw if raw else default name = input_with_default("请输入姓名", "匿名")

这也是实际项目中很常用的小技巧,比让用户重新输入、判断空字符串要优雅得多。


最后说点个人体会。我在给模拟项目X做命令行交互工具的时候,早期代码写得相当随意——input()结果不校验、文件不关、编码不指定、print 不带格式。后来被数据丢失、乱码、缓冲区问题反复折磨过几次,才意识到输入输出模块处于“简单但重要”的特殊位置:它是程序对外的脸面,也是数据流的咽喉。文中这些坑,几乎都是实际踩过才总结出来的,你没必要再踩一遍。把这讲内容吃透并动手把记账本跑通,你对 Python 程序交互的理解会有一个明显提升。接下来整个系列会进入文件操作和数据处理部分,到时这些基本功会全部派上用场。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 5:42:05

AI Skill怎么找?从来源筛选到自建的全流程实操指南

1. 找 Skill 这件事&#xff0c;为什么比想象中难最近不少朋友在群里问同一个问题&#xff1a;“在哪儿找 Skill 比较靠谱&#xff1f;”问的人多了&#xff0c;我才意识到这其实不是一个“伸手党”问题&#xff0c;而是很多人真正卡住的地方。Skill 这个概念最近确实火&#x…

作者头像 李华
网站建设 2026/10/14 5:41:51

六边云教你Docker云手机ADB访问和网页Web VNC访问区别

搭建 Docker 云手机&#xff08;例如六边云手机&#xff09;的时候&#xff0c;有两种最常用远程访问方式&#xff1a;网页 Web VNC&#xff08;6080 端口&#xff09; 和 ADB 远程调试&#xff08;5555 端口&#xff09;。很多新手分不清两者定位&#xff0c;要么全部开放带来…

作者头像 李华
网站建设 2026/10/14 5:41:32

主流大模型实测对比:成本、效果、稳定性与选型思路

我陆续把手里几个大模型都用成了常驻工具&#xff0c;今年以来几乎每周都在重新做对比测试。这个事说白了没有一劳永逸的答案&#xff1a;模型更新太快&#xff0c;今天的最优解下个月可能就变“次优”。但恰恰是这种频繁对比的过程&#xff0c;让我摸清了很多跑分榜单看不出来…

作者头像 李华
网站建设 2026/10/14 5:41:14

易语言阶乘与组合数实现:数据类型溢出问题的四种方案

前阵子在易语言社区里刷到一个高频问题&#xff1a;怎么用易语言算阶乘和组合数&#xff1f;这题看起来就是数学课作业的级别&#xff0c;真动手做起来却没想象中那么轻松。我一开始也觉得写个阶乘函数、套个公式就完事了&#xff0c;结果被数据类型的溢出问题结结实实上了一课…

作者头像 李华
网站建设 2026/10/14 5:41:13

本地 AI 智能体 OpenClaw 实操,五分钟完成 Windows 部署

Windows 一键部署 OpenClaw 教程&#xff5c;5 分钟搞定本地 AI 智能体&#xff0c;告别复杂配置 说明&#xff1a;本文适配 CSDN 发布&#xff0c;全部版本号、链接、文件大小已替换&#xff0c;删除标签 / 日期 / 阅读量 / 示意图&#xff0c;规避禁用词汇&#xff0c;首次全…

作者头像 李华
网站建设 2026/10/14 5:40:22

Python实战:Flask与TensorFlow构建网页聊天机器人完整链路

先确认一个现实问题&#xff1a;很多人做聊天机器人&#xff0c;要么拿现成API一把梭&#xff0c;要么只写个命令行的测试Demo&#xff0c;真正想做成“网页里可以对话”的产品形态时&#xff0c;却卡在不知道怎么把Web端和模型端串起来。这期实战就是用Flask做Web服务、Tensor…

作者头像 李华