
1. 为什么建议从txt和csv入手文件操作的底层逻辑1.1 文件读写是什么大多数教程没讲透的那层窗户纸先聊一个很多人入门时没搞明白的事文件操作到底在操作什么你在Python里写a 1这个变量存在于内存中程序一关就没了。但如果你想把数据留下来下次打开程序还能用就必须把数据写到硬盘上也就是写入文件。反过来程序启动时要读取已有的数据同样要从硬盘读文件到内存里。这一写一读就是文件操作的核心。那为什么我强烈建议新手从txt和csv入手因为这两种格式是纯文本格式直接用记事本就能打开看。你可以随时用肉眼检查写入是否正确、读出来是否符合预期这对于建立“代码和数据流”的心智模型特别有帮助。相比之下如果你一上来就用Excel二进制格式或者SQLite数据库出了问题很难判断是代码逻辑错了还是数据本身写坏了。我见过不少新人一上来就追着问“怎么用Python操作Excel”结果折腾半天装库、处理格式、踩各种坑。我的建议是先踏踏实实把txt和csv玩熟了因为这两个格式覆盖了日常80%以上的数据交换需求而且学到的思路——打开文件、读写、关闭、处理编码——在操作任何其他格式时都完全通用。1.2 你只需要先掌握三个核心步骤不管读还是写Python文件操作都逃不开三个步骤打开文件open读或写数据read / write关闭文件close就这么简单。但实际写代码时很多人会漏掉第三步或者打开文件后忘记关闭导致别人占用文件、内容没写进去、内存增长等问题。后面我会讲怎么用with语句彻底解决这个烦恼这是新手必须养成的好习惯。另外还有一个概念需要尽早理解文件对象file object。当你用open()打开文件时返回的不是文件内容本身而是一个“管道”对象。你可以把它理解成一根连接程序和文件的吸管读操作就是通过吸管往外吸内容写操作就是往吸管里吹内容。读完之后吸管到达文件末尾再读就只会得到空字符串想重新读得把“吸管位置”移回开头seek(0)或者重新打开文件。理解了这三个步骤和文件对象的概念你已经掌握了文件操作的骨架。接下来我们把环境准备好然后一个一个动手实操。2. 动手前的准备工作环境搭建与路径问题2.1 环境搭建没那么复杂别被教程吓退很多新手都会被网上各种Python安装教程搞晕一会儿配环境变量一会儿装Anaconda一会儿用虚拟环境。其实入门阶段你只需要一个能运行Python的环境就行了最简单的方案是两个去Python官网下载安装包安装时勾选“Add Python to PATH”选项装完打开命令行输入python --version确认版本号。安装一个编辑器推荐VSCode因为它是免费的而且对Python的支持很好写完代码按一下右键选“在终端中运行Python文件”就能跑。这里我不展开讲IDE的详细配置因为真正困扰新手的往往不是环境而是后面要讲的路径和编码问题。但有一点我想强调安装Python时一定记得勾选“Add Python to PATH”。如果你没勾后面在命令行里输入python会提示“不是内部或外部命令”这对新人来说非常劝退。2.2 文件路径的三个常见坑一次说清楚文件操作绕不开路径而路径恰是新手最容易翻车的点。我总结最常见的三个坑坑一相对路径和当前工作目录搞不清假设你的Python脚本在D:/project/demo.py你写open(data.txt)Python并不是在脚本所在目录找data.txt而是在“当前工作目录”找。这个当前工作目录通常是你启动Python程序时所在的目录在VSCode里可能是你打开的那个文件夹在命令行里就是你敲命令时所在的目录。一个快速排查技巧运行import os; print(os.getcwd())查看程序运行时真正的当前目录。如果发现文件找不到多半就是这里出了问题。坑二Windows路径里的反斜杠Windows的路径是D:\data\file.txt但在Python字符串里反斜杠\是转义字符\d、\f会被解析成特殊含义导致路径报错。解决办法有几种用正斜杠D:/data/file.txtWindows完全兼容用原始字符串rD:\data\file.txt用双反斜杠D:\\data\\file.txt我自己最常用的是正斜杠因为省事、跨平台兼容性也好。坑三文件夹不存在就报FileNotFoundError写入文件时如果目标文件夹不存在Python不会自动帮你创建目录而是直接报错。这事我刚开始也踩过后来养成了习惯写文件前先判断目录是否存在不存在就用os.makedirs()创建。代码大概是这样的import os output_dir output if not os.path.exists(output_dir): os.makedirs(output_dir) with open(f{output_dir}/result.txt, w, encodingutf-8) as f: f.write(hello)这招在后续你做批量处理、结果导出时尤其管用。2.3 理解绝对路径和相对路径的适用场景简单说绝对路径从盘符或根目录开始写相对路径从当前工作目录开始写。我建议脚本内部优先使用相对路径这样整个项目复制到别的电脑上也能跑不需要改路径而只有涉及固定的数据源、外部输入时才使用绝对路径。如果你用绝对路径最好用pathlib模块来管理它比手工拼字符串靠谱得多from pathlib import Path base_dir Path(__file__).parent # 脚本所在目录 data_path base_dir / data / input.csvPath对象重载了/运算符拼接路径特别直观而且Path(__file__).parent能拿到脚本的绝对目录不受当前工作目录影响。这个组合是实战中非常常用的写法建议新手尽早用起来。3. 读取txt文件从基础写法到异常处理3.1 最基础的三种读取方式以及它们各自适合什么场景读取txt文件核心就一个函数open()然后根据你的需求选read()、readline()还是readlines()。# 方式一一次性读取全部内容 with open(novel.txt, r, encodingutf-8) as f: content f.read() print(content[:500]) # 打印前500个字符 # 方式二逐行读取到列表 with open(novel.txt, r, encodingutf-8) as f: lines f.readlines() print(len(lines)) # 文件总行数 # 方式三迭代读取每一行推荐 with open(novel.txt, r, encodingutf-8) as f: for line in f: print(line.strip())三种方式的适用场景不一样read()适合小文件把全文读进内存再处理方便做全文搜索、替换等操作。但如果文件是几个GB一次读进来内存直接爆掉。readlines()适合需要按行索引访问的场景比如我要访问第3行、第10行的数据。它会一次性把所有行读成一个列表同样对超大文件不友好。for line in f是实战中最推荐的它底层是惰性读取一次只读一行到内存无论文件多大都不会爆内存。另外注意一个细节读取到的每一行末尾通常带有换行符\n。写入文件时用的换行符读出来是什么就是什么所以打印时经常发现多了个空行这就是因为内容里本身就带着\n。用strip()可以把行首尾的空白字符包括换行符和空格去掉。3.2 with语句为什么你必须从现在开始用起来前面说过打开文件后要关闭文件。但现实是很多人写着写着就忘了f.close()或者代码中途报错还没执行到close语句文件就已经open状态了。这种问题有两个后果一是文件被占用其他程序无法删除或修改它二是内容可能只写进了缓冲区没真正落盘。with语句就是来解决这个问题的。它的学名叫“上下文管理器”你不需要完全理解它内部的机制只要记住一个结论用with open(...) as f:打开文件后无论代码是正常执行完还是中途抛出异常Python都会自动帮我们关闭文件。# 不要这样写 f open(test.txt, r, encodingutf-8) content f.read() f.close() # 容易忘 # 要这样写 with open(test.txt, r, encodingutf-8) as f: content f.read()养成用with的习惯是文件操作从“能跑”到“靠谱”的分水岭。我后来看别人代码只要看到裸open()没有配with第一反应就是建议Ta改掉。3.3 读取大文件时的一个高效技巧如果你要处理的是几个GB的日志文件直接readlines()会让内存占用飙升甚至卡死电脑。正确做法是逐行迭代处理边读边丢弃count 0 with open(huge_log.txt, r, encodingutf-8, errorsignore) as f: for line in f: # 在这里处理每一行比如统计行数、筛选关键词 count 1 if ERROR in line: print(line.strip()) print(f总行数{count})这里顺便提一下errorsignore参数。处理真实数据时文件里难免混入一些非法编码的字节如果严格按照编码解析会直接抛异常导致整个程序中断。加到errorsignore后遇到无法解析的字节会选择忽略程序能继续跑下去。这招在处理爬虫下载的网页、日志文件时特别实用但也意味着可能会丢字符所以什么时候用取决于你的容错策略。3.4 编码问题UTF-8和GBK的恩怨情仇说txt文件绕不开编码。简单理解编码就是字符和二进制之间的映射规则。Windows记事本默认的编码可能是GBK而Python 3里open()默认用的是系统编码Windows上通常是GBK这就导致一个经典问题你在Windows上创建一个txt文件用UTF-8去读读出来全是乱码反过来也一样。处理原则很简单知道文件编码时在open()里明确指定encodingutf-8或encodinggbk不确定编码时先用记事本打开文件看看右下角显示的是什么编码跨平台传输、存数据库、做数据分析一律统一用UTF-8另外有个老程序员才知道的坑UTF-8 BOM。有些编辑器尤其是Windows记事本保存UTF-8文件时会在文件开头塞三个字节的BOM头\xef\xbb\xbfPython按UTF-8读取时会把这几个字节当成一个不可见字符读进来导致你读出来的第一行第一个字符是\ufeff。处理办法是用encodingutf-8-sig来读取Python会自动去掉BOM头。如果你是用UTF-8写文件、且不希望出现BOM头使用encodingutf-8就行Python不会自动加BOM。4. 写入txt文件覆盖与追加的正确姿势4.1 写入模式的选择是每个新手都要过的第一关Python的open()函数有个mode参数决定你以什么方式打开文件。写入相关的常用模式就三个模式作用文件是否存在文件不存在w写入覆盖原有内容清空后写入创建新文件a追加在末尾添加内容末尾追加创建新文件x创建新文件已存在则报错报错创建新文件这个表格看起来简单但实际用起来很容易踩坑。我举个例子你想往日志文件里追加一行记录但用了w模式结果把之前所有的日志全清光了。这种事故我见过不止一次。所以选模式前先问自己一个问题这个文件是全新生成还是在已有数据基础上追加生成新文件、覆盖旧文件 → 用w在现有文件末尾加内容日志、记录 → 用a防止误覆盖已有文件 → 用x写文件的基础代码是with open(output.txt, w, encodingutf-8) as f: f.write(第一行\n) f.write(第二行\n)注意write()不会自动加换行符你需要自己写\n。这和print()函数的行为不一样很多人第一次写文件都会忘记结果所有内容挤在一行。4.2 批量写入与缓冲区的秘密如果你要写入很多行内容用writelines()比循环调用write()更高效lines [第一行\n, 第二行\n, 第三行\n] with open(output.txt, w, encodingutf-8) as f: f.writelines(lines)注意writelines()接收的是字符串列表而且列表里的每个字符串需要自带换行符。关于性能还有一个你可能没注意到的知识点写入操作是带缓冲的。意思是f.write(hello)之后数据可能还躺在内存缓冲区里没有真正写到硬盘上。触发真正写入硬盘的时机有三个缓冲区满了调用f.flush()强制刷新文件关闭包括with块结束在绝大多数场景下你不需要关心这个机制因为with块退出时会自动刷新并把数据落盘。但如果你需要“写一行立刻出现在文件里”的效果——比如写日志时另一个程序在实时读取这个文件——就需要手动flush()一下。也可以直接在open()里加上第三个参数buffering0但这样每次写入都直接操作硬盘性能会明显下降不建议在批量写入时用。4.3 一个现实场景把爬虫结果追加到txt写文件最典型的场景就是保存程序运行结果。举个例子爬虫抓取了一批文章标题需要把结果追加到同一个txt文件里import time def save_title(title): timestamp time.strftime(%Y-%m-%d %H:%M:%S) with open(titles.txt, a, encodingutf-8) as f: f.write(f{timestamp}\t{title}\n) save_title(Python文件操作入门) save_title(csv模块使用技巧)这种写法用a模式每次运行把新标题追加到文件末尾不会覆盖历史数据而且带时间戳方便追踪记录。用\t做分隔符后续读出来用split(\t)就能分开。5. csv模块读取比手动split更正规、更省心5.1 为什么不用split去切字符串很多新手拿到csv文件后第一个想法是with open(data.csv, r, encodingutf-8) as f: for line in f: fields line.strip().split(,) print(fields)如果只是临时看一下数据这么写没毛病。但一旦你的csv里某个字段值带了逗号比如北京, 中国被当成一列用split(,)就会把这个字段切成两半列数就对不上了。更坑的是如果字段值里还有引号、换行符手工解析会让人崩溃。csv标准格式远不止“逗号分隔”这么简单字段内容可能包含逗号、引号、换行符字段可能用引号包裹引号内的逗号不应当作分隔符各种符号可能需要转义手工处理这些边界情况会非常痛苦而Python标准库的csv模块就是专门干这个的。它帮你处理了所有格式细节而且因为它是标准库不需要额外安装。5.2 csv.reader的基础用法逐行读取使用csv模块读取文件非常简单import csv with open(students.csv, r, encodingutf-8) as f: reader csv.reader(f) for row in reader: print(row)reader返回的是一个迭代器每次迭代会返回一个列表包含这一行的所有字段。举个例子假设students.csv内容是姓名,语文,数学,英语 张伟,88,92,79 李娜,95,87,91读取结果的每一行会是这样[姓名, 语文, 数学, 英语] [张伟, 88, 92, 79] [李娜, 95, 87, 91]注意所有字段读出来都是字符串类型即使内容是数字。如果后续要计算需要自己做类型转换。5.3 大文件处理与性能优化处理大csv文件时和txt文件一样推荐逐行迭代因为csv.reader本身就是惰性的一行一行处理内存占用很小import csv total_score 0 count 0 with open(scores.csv, r, encodingutf-8) as f: reader csv.reader(f) header next(reader) # 跳过表头 for row in reader: total_score int(row[1]) # 假设第二列是分数 count 1 print(f平均分{total_score / count:.2f})这里用next(reader)跳过表头是非常常用的操作。如果你知道文件有表头但又不确定也可以用next(reader, None)的方式安全跳过。5.4 csv.DictReader让代码告别魔法数字csv.reader返回的是列表访问字段靠下标比如row[2]。如果csv的列数变了或者你记不清第几列是什么字段代码就不太好维护。更优雅的方式是用csv.DictReader它会把第一行当表头每一行返回一个字典字段名就是列名。import csv with open(students.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: print(f{row[姓名]} 数学成绩{row[数学]})这样写的好处特别明显通过列名访问代码可读性大幅提升表头顺序变了也不影响只要列名还在某些列缺失时对应的值会是None方便处理我在实际项目中几乎都用DictReader除非遇到没有表头的csv文件。没有表头时可以给DictReader传一个fieldnames参数手动指定列名。5.5 一个实战例子筛选打卡记录举个大家都会遇到的场景从考勤记录csv中筛选出迟到的人并打印。假设checkin.csv内容是工号,姓名,打卡时间 001,张伟,2024-01-15 09:15:00 002,李娜,2024-01-15 08:57:00 003,王强,2024-01-15 09:30:00代码如下import csv with open(checkin.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: time_str row[打卡时间].split( )[1] # 取出时间部分 hour, minute time_str.split(:)[:2] minutes int(hour) * 60 int(minute) if minutes 9 * 60: # 9:00之后算迟到 print(f{row[工号]} {row[姓名]} 迟到了打卡时间{row[打卡时间]}) # 输出 # 001 张伟 迟到了打卡时间2024-01-15 09:15:00 # 003 王强 迟到了打卡时间2024-01-15 09:30:00这个例子综合了csv读取、字典访问、字符串处理和条件判断是很好的入门练习。6. csv文件写入从基础到数据清洗实战6.1 csv.writer基础写入写入中文别忘编码写入csv文件和读取类似用csv.writer但要特别注意newline参数。import csv with open(output.csv, w, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerow([姓名, 语文, 数学]) writer.writerow([张伟, 88, 92]) writer.writerow([李娜, 95, 87])如果不加newlineWindows上每次写入行之间会多出一个空行。这个问题的原因和文件读写时的换行处理有关但结论很简单用csv模块写文件一定加newline。另外关于编码如果csv文件后续要用Excel打开建议用utf-8-sig编码这样Excel不会把中文识别成乱码。如果文件是给程序读的用utf-8就够了。这也是写程序和写“人要用Excel看的文件”之间的一个区别网上很多人没讲清楚。6.2 csv.DictWriter按表头写入结构化数据和读取端对应写入端也有DictWriter。它特别适合把字典列表写入csv让代码更清晰import csv students [ {姓名: 张伟, 语文: 88, 数学: 92}, {姓名: 李娜, 语文: 95, 数学: 87}, ] fieldnames [姓名, 语文, 数学] with open(students.csv, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() # 写入表头 for student in students: writer.writerow(student)注意DictWriter需要你显式传递fieldnames它就是列的顺序。如果某个字典缺了fieldnames里的键writerow会报错反过来字典里多了fieldnames没有的键默认会被忽略。如果想调整这个行为可以传extrasactionignore参数。6.3 从txt清洗数据写入csv一个完整的实战案例为了把读取和写综合起来我们来看一个日常很常见的任务有一个格式乱七八糟的txt文件需要清洗后转成csv。假设raw_data.txt内容长这样姓名 语文 数学 英语 张伟 88 92 79 李娜 95 87 91 王强 67 73 68列之间是用多个空格分隔的还可能有行首行尾的空格。目标是清洗成标准的csv文件。import csv rows [] with open(raw_data.txt, r, encodingutf-8) as f: for line in f: # 按空格切分自动合并连续空格 fields line.split() if fields: rows.append(fields) with open(cleaned_data.csv, w, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerows(rows) print(转换完成)这里的split()不加参数时会自动按任意空白字符切分并忽略连续的空白和首尾空白完美解决“多空格分隔”的问题。writerows(rows)接收一个二维列表一次性写入多行比循环调用writerow()更高效。运行完用Excel打开cleaned_data.csv你会发现数据被整理得干干净净。这个案例虽然简单但它体现了一个完整的处理流程读取原始数据 - 清洗 - 写入结构化文件。你在工作中写的数据处理脚本本质上都是这个模式的变体。6.4 性能对比writerows与循环writerow刚才提到了writerows这里多聊一句性能。如果你要写入几万行数据writerows和循环writerow的效率差距其实没有想象中大因为csv模块本身已经做了缓冲。但在代码简洁性上writerows完胜。所以我的建议是能用一次writerows就别循环写当数据量极大几十万行以上时再去考虑分批写入或数据库方案。7. 常见问题与排查技巧实录7.1 问题速查表我把平时带新人的时候遇到最多的文件操作问题整理成了一张表先说结论后面挑几个典型的展开讲现象常见原因解决方法FileNotFoundError路径不对或文件不存在打印os.getcwd()确认当前目录检查相对路径PermissionError文件被Excel/WPS占用先关闭占用文件的程序中文乱码编码不匹配统一使用encodingutf-8或gbk读文件前先确认编码第一行多一个\ufeff文件带UTF-8 BOM头用encodingutf-8-sig读取UnicodeDecodeError文件里有非法编码字节加errorsignore参数csv每隔一行空一行写文件没加newlineopen()中加newline字符串带\n导致打印多空行行末换行符未去掉用strip()或rstrip()写入的内容没出现在文件里缓冲区未刷新用with块或用flush()强制刷新csv列数对不上手动split(,)拆坏了用csv.reader7.2 排查思路先确认定位问题再动手改代码遇到文件操作报错先别急着改代码按照下面这个顺序排查第一步确认“文件系统层”是否有问题。文件是否真的存在、路径是否正确、当前工作目录在哪。这一步可以用os.path.exists()快速验证。import os print(os.path.exists(data.csv)) # True/False print(os.getcwd()) # 当前工作目录第二步确认“打开参数”是否正确。编码对不对、模式对不对是读还是写、覆盖还是追加、newline参数有没有加。第三步如果前面都正常就是“内容层”的问题。比如文件里混入非法字符、csv格式不规范等。可以先用记事本打开文件肉眼确认它长什么样再决定代码怎么写。7.3 两个容易忽略的坑close时机和文件对象偏移说实话新手最容易困惑的是“为什么读出来的数据是空的”。比如这样一段代码with open(data.txt, r, encodingutf-8) as f: content f.read() print(content) # 第一次读取正常 print(f.read()) # 第二次读取是空的第二次read()返回空字符串是因为文件对象指针已经移动到了文件末尾。如果你想重新读取内容需要f.seek(0)把指针移回开头或者干脆重新open()。还有一个场景先写文件再读文件忘了重新打开。比如# 错误的示范 f open(test.txt, w, encodingutf-8) f.write(hello) content f.read() # 报错以w模式打开的文件不可读如果你想同时读写一个文件应该用r、w等模式。不过我不建议新手用这些模式逻辑容易混乱。更清晰的做法是分开操作写完先关闭再以读模式打开各干各的各不干扰。7.4 编码检测小技巧用Python判断文件编码遇到一个未知编码的文件你不用挨个试。可以利用Python库来检测编码常见的是chardet库需安装。不过对于入门来说一个简单粗暴的办法是先尝试UTF-8读取报错了再尝试GBK再报错就尝试latin-1。写成代码大概是这样for encoding in [utf-8, gbk, latin-1]: try: with open(unknown.txt, r, encodingencoding) as f: content f.read() print(f编码是{encoding}) break except UnicodeDecodeError: continue这种“顺序尝试”的思路在解决编码问题时很实用以后遇到各种稀奇古怪的编码问题都可以沿用这个模式。8. 最后的实战心得一个把这一切串起来的综合案例文章快结束了我想用一个贴近真实工作的例子把前面讲的内容串起来。这个任务是我之前辅导一个朋友时遇到的他手上有几百个txt格式的销售记录现在要汇总到一张csv表里而且txt的格式还不完全一致。数据大概是这样的sales_202401.txt 2024-01-01, 张三, 华东, 128.50 2024-01-02, 李四, 华北, 89.00 2024-01-03, 张三, 华东, 200.00另一个文件可能是2024-01-01 王五 华南 75.20 2024-01-02 赵六 西南 301.00有逗号分隔的有空格分隔的统一处理需要先按逗号split不行再按空格split。最终代码可以这样写import csv import glob all_rows [] for file_path in glob.glob(sales_*.txt): with open(file_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue # 先按逗号分隔如果不是两段以上再按空格 fields line.replace( , ).split(,) if len(fields) 4: fields line.split() if len(fields) 4: all_rows.append(fields) with open(all_sales.csv, w, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerow([日期, 销售员, 区域, 金额]) writer.writerows(all_rows) print(f共处理 {len(all_rows)} 条记录)这个代码用到了glob.glob()批量匹配文件名with语句管理文件资源strip()处理行首尾空白对不同分隔格式做兼容处理csv.writer写入标准csv文件流程无非就是打开txt - 逐行清洗 - 收集到列表 - 写csv。你看和前面讲的基础完全一样只是多了一点判断和循环。这就是文件操作的实际应用——技术点本身不多关键是你怎么把它们组合起来解决真实问题。根据我的经验初学者以这个综合案例为目标倒推学习比单纯背语法快得多。拿到代码每一步都手动跑一遍输出打印出来看你就能真正理解open、read、write、csv这些概念在实际中是怎么配合的。动手跑通一个小项目远比看十篇教程要有效。最后如果你想把文件操作练熟我建议自己找一点真实数据练手比如把朋友圈的聊天记录导出来存成txt然后写脚本统计关键词频率或者把通讯录整理成csv再写个查询脚本。从自己身边的需求出发你会发现文件操作一点都不枯燥而且特别容易有成就感。