📊 阅读时长:20分钟 | 关键词:Python文件操作、open()、读写模式、with语句、os模块、路径处理
引言:和计算机关打交道的唯一方式
前面讲的都是"计算"——加减乘除、列表操作、函数调用。但程序真正强大之处,在于能和文件系统打交道:
- 读取配置文件(
.ini、.json、.yaml) - 处理用户上传的文件(
.csv、.xlsx、图片) - 生成报表输出到本地(
.txt、.pdf、.docx) - 日志系统(
.log文件的读写)
Python 的文件操作 API 设计得非常优雅,学完这篇,你基本上能应付 90% 的文件处理任务。
一、路径操作:文件在哪?
在操作文件之前,你得先搞清楚文件在哪——这就是路径做的事。
1.1 绝对路径 vs 相对路径
| 类型 | 说明 | 示例(Windows) | 示例(macOS/Linux) |
|---|---|---|---|
| 绝对路径 | 从根目录开始的完整路径 | D:\PythonFiles\p01.py | /home/user/code/main.py |
| 相对路径 | 相对于当前工作目录的路径 | .\data\input.txt | ./data/input.txt |
获取当前工作目录:
importos cwd=os.getcwd()print(cwd)# 输出当前工作目录相对路径的特殊符号:
.→ 当前目录..→ 上一级目录
os.getcwd()# D:\PythonFilesos.path.join('.','data','input.txt')# .\data\input.txtos.path.join('..','main.py')# ..\main.py1.2os.path常用方法速查
importos path=r'D:\PythonFiles\data\input.txt'os.path.basename(path)# 'input.txt' → 最后一级(文件名)os.path.dirname(path)# 'D:\PythonFiles\data' → 目录部分os.path.split(path)# ('D:\...\data', 'input.txt')os.path.splitext(path)# ('D:\...\input', '.txt') → 拆分扩展名os.path.exists(path)# False(不存在)os.path.isfile(path)# Falseos.path.isdir(path)# False📸[图1:
os.path各方法解析效果对比图]
建议配图:一张表中列出同一个路径字符串,分别用basename/initname/split/splitext处理后得到的返回值,直观展示每个方法"切"出来的部分。
1.3os.path.join():智能拼接路径(跨平台必备)
永远不要用字符串拼接路径!不同操作系统的路径分隔符不同(Windows 用\,macOS/Linux 用/)。os.path.join()会根据当前操作系统自动选择正确的分隔符。
importos# ❌ 错误写法(不跨平台)path='data'+'\\'+'input.txt'# 在 Mac 上会出错# ✅ 正确写法path=os.path.join('data','input.txt')print(path)# Windows → data\input.txt# macOS/Linux → data/input.txt1.4 创建目录:os.makedirs()
importos# 创建单层目录os.mkdir('new_folder')# 递归创建多层目录(自动创建中间目录)os.makedirs('./dir1/dir2/dir3',exist_ok=True)# exist_ok=True:目录已存在时不报错二、open():打开文件的万能钥匙
2.1 基本语法
file=open(file,mode='r',encoding=None)| 参数 | 说明 |
|---|---|
file | 文件路径(绝对或相对) |
mode | 打开模式,默认'r'(只读) |
encoding | 文本文件编码,建议显式指定'utf-8' |
返回一个文件对象(迭代器对象),可以用for循环逐行读取。
2.2 文件打开模式详解
| 模式 | 说明 | 文件不存在时 | 文件已存在时 |
|---|---|---|---|
'r' | 只读 | 报错FileNotFoundError | 正常打开,指针在开头 |
'w' | 只写(覆盖) | 创建新文件 | 清空原内容再写入 |
'a' | 只写(追加) | 创建新文件 | 指针在末尾,追加写入 |
'x' | 独占创建 | 创建新文件 | 报错FileExistsError |
'b' | 二进制模式 | — | 需配合r/w/a使用 |
'+' | 读写模式 | — | 需配合r/w/a使用 |
组合模式示例:
open('file.txt','rb')# 二进制只读(如读取图片)open('file.txt','wb')# 二进制只写open('file.txt','r+')# 读写,指针在开头open('file.txt','w+')# 读写,清空后写入open('file.txt','a+')# 读写,指针在末尾2.3 文本文件 vs 二进制文件
| 类型 | 编码 | 示例文件 | 打开模式 |
|---|---|---|---|
| 文本文件 | 需要指定encoding | .txt、.py、.json、.md | 'r'/'w'/'a'(默认) |
| 二进制文件 | 无编码,直接读写字节 | .png、.mp3、.exe、.zip | 'rb'/'wb'/'ab' |
三、文件对象的常用方法
3.1 读取内容
withopen('./exam.txt','r',encoding='utf-8')asf:# 方式1:next() 读取一行(文件是迭代器)print(next(f))# 方式2:for 循环逐行读取(最常用,内存友好)forlineinf:print(line,end='')# end='' 去掉多余的换行# 方式3:read(size) 读取指定字符数f.seek(0)# 指针回到开头content=f.read(5)# 读取前5个字符print(content)# 方式4:read() 读取全部f.seek(0)all_content=f.read()print(all_content)# 方式5:readlines() 读取全部行到列表f.seek(0)lines=f.readlines()print(lines)# ['line1\n', 'line2\n', ...]📸[图2:文件指针移动示意图]
建议配图:一个横向的文本流,标注指针位置(seek),展示 read(5) 从指针处读取5个字符后指针移动到第6个字符位置。
3.2 写入内容
withopen('./exam.txt','w',encoding='utf-8')asf:# write(s):写入字符串,返回写入的字符数num=f.write('Hello World\n')print(f'写入了{num}个字符')# writelines(lines):写入字符串列表(不会自动加换行!)lines=['Line 1\n','Line 2\n','Line 3\n']f.writelines(lines)⚠️ 注意:writelines()不会自动添加换行符,需要自己加\n。
3.3 移动文件指针:seek(offset)
withopen('./exam.txt','r+',encoding='utf-8')asf:f.write('Hello')f.seek(0)# 指针回到开头print(f.read())# 从头开始读取3.4 刷新缓冲区:flush()
写入文件时,Python 会先把内容放到缓冲区,等缓冲区满了或文件关闭时才真正写入磁盘。flush()可以强制立即写入。
importtime f=open('./test.txt','a',encoding='utf-8')f.write('123456789\n')f.flush()# 立即写入磁盘(不等文件关闭)time.sleep(5)# 这5秒内文件里已经有内容了f.close()四、with语句:最优雅的文件操作方式
4.1 为什么要用with?
传统写法需要手动关闭文件,如果中间发生异常,close()可能执行不到:
# ❌ 不推荐的写法f=open('./test.txt','w',encoding='utf-8')f.write('hello world')# 如果这里发生异常,close() 不会执行 → 文件损坏风险f.close()with语句的好处:无论是否发生异常,退出with块时自动执行close()。
# ✅ 推荐写法withopen('./test.txt','w',encoding='utf-8')asf:f.write('hello world')# 退出 with 块时,自动调用 f.close()等价于:
f=open('./test.txt','w',encoding='utf-8')try:f.write('hello world')finally:f.close()# 无论如何都会执行4.2with+try/except/finally组合
try:withopen('./test.txt','r',encoding='utf-8')asf:content=f.read()print(content)exceptFileNotFoundError:print('文件不存在!')exceptUnicodeDecodeError:print('文件编码不是 UTF-8!')finally:print('文件操作结束(自动关闭)')五、实战:几个常用文件操作场景
5.1 复制文件
defcopy_file(src,dst):"""复制文件(支持文本和二进制)"""withopen(src,'rb')asfsrc:withopen(dst,'wb')asfdst:whileTrue:chunk=fsrc.read(4096)# 每次读 4KBifnotchunk:breakfdst.write(chunk)print(f'已复制:{src}→{dst}')copy_file('./source.jpg','./backup/copy.jpg')5.2 逐行处理日志文件(内存友好)
# 处理大文件时,绝对不能用 read() 一次性读入内存!# 用 for 循环逐行读取,每次只占用一行的内存error_count=0withopen('./app.log','r',encoding='utf-8')asf:forlineinf:# 逐行迭代,内存占用极低if'ERROR'inline:error_count+=1print(f'发现错误:{line.strip()}')print(f'共发现{error_count}处错误')5.3 CSV 文件读写(数据分析必备)
importcsv# 读取 CSVwithopen('./data.csv','r',encoding='utf-8')asf:reader=csv.reader(f)header=next(reader)# 跳过表头print(f'表头:{header}')forrowinreader:print(row)# 写入 CSVwithopen('./output.csv','w',newline='',encoding='utf-8')asf:writer=csv.writer(f)writer.writerow(['姓名','年龄','城市'])# 写表头writer.writerows([['小明',25,'北京'],['小红',23,'上海'],])六、动手练习
练习 1:统计文件行数
defcount_lines(filepath):"""统计文件行数"""withopen(filepath,'r',encoding='utf-8')asf:returnsum(1for_inf)# 逐行迭代计数print(count_lines('./exam.txt'))练习 2:查找包含关键词的行
defgrep(filepath,keyword):"""查找包含关键词的所有行"""withopen(filepath,'r',encoding='utf-8')asf:fori,lineinenumerate(f,1):ifkeywordinline:print(f'第{i}行:{line.strip()}')grep('./app.log','ERROR')练习 3:批量重命名文件
importosdefbatch_rename(folder,old_ext,new_ext):"""批量修改文件扩展名"""forfilenameinos.listdir(folder):iffilename.endswith(old_ext):old_path=os.path.join(folder,filename)new_filename=filename.replace(old_ext,new_ext)new_path=os.path.join(folder,new_filename)os.rename(old_path,new_path)print(f'{filename}→{new_filename}')batch_rename('./images','.jpeg','.jpg')小结
| 知识点 | 一句话总结 |
|---|---|
| 路径操作 | 用os.path.join()拼接路径,跨平台 |
open() | 打开文件,返回文件对象;指定mode和encoding |
| 读写模式 | 'r'只读 /'w'覆盖 /'a'追加 /'b'二进制 |
| 读取方法 | read()全读 /readline()读一行 /for line in f逐行(推荐) |
| 写入方法 | write()写字符串 /writelines()写列表 |
with语句 | 自动关闭文件,异常处理更安全,必须用 |
| 大文件处理 | 用for line in f逐行读取,不要用read() |
📸[图3:文件操作流程全景图]
建议配图:一张完整的流程图,从open()打开文件 → 选择读写模式 → 调用 read/write 方法 → 关闭文件(close 或 with 自动关闭),并在底部标注"文本模式 vs 二进制模式"的区别。
本文是「Python从入门到数据分析」系列的第 13 篇,共 24 篇。关注我,不错过后续更新。