文件读写
1.知识点
核心概念:一切文件操作都走这三步
打开文件——>读写内容——>关闭文件
python把文件看作一个“字符流”,读的时候像水龙头的水一样流出来,写的时候像往里面倒水一样流出去。内部有一个文件指针,记录当前读/写到了哪个位置。
open()模式速查
| 模式 | 含义 | 文件不存在时 | 文件存在时 | 读写位置 |
|---|---|---|---|---|
| r | 只读(默认) | 报错 | 打开 | 开头 |
| w | 只写 | 创建新文件 | 清空院内容 | 开头 |
| a | 追加写 | 创建新文件 | 不覆盖,追加末尾 | 末尾 |
| x | 独占创建写 | 创建新文件 | 报错 | 开头 |
| r+ | 读写 | 报错 | 打开,不覆盖 | 开头 |
| w+ | 读写 | 创建新文件 | 清空原内容 | 开头 |
| a+ | 读写追加 | 创建新文件 | 不覆盖 | 末尾 |
注意事项:
'w'模式像一张白纸,一打开就把原有内容擦了,小心使用'a'模式像在日记本最后一页继续写,安全得多- 加
'b'后缀(如'rb')进入二进制模式,用于图片、视频等非文本文件- 加
't'后缀(如'rt')显式指定文本模式,这是默认值,通常省略
with语句(上下文管理器)
withopen('data.txt','r',encoding='utf-8')asf:content=f.read()# 出了 with 块,文件自动关闭,不需要手动 f.close(好处:即使读取过程中抛出异常,文件也会被正确关闭。就像图书馆的自动门——进去时自动开,出来自动关,绝不会忘了锁门。
读方法对比
| 方法 | 返回类型 | 一次读取量 | 适用场景 |
|---|---|---|---|
read() | 字符串 | 整个文件 | 小文件(< 几 MB) |
read(n) | 字符串 | n 个字符 | 按量分批读取 |
readline() | 字符串 | 一行 | 逐行处理,需手动循环 |
readlines() | 列表 | 所有行 | 小文件,需要随机访问某行 |
直接迭代for line in f | 字符串 | 一行一行 | 大文件首选,内存友好 |
写方法对比
| 方法 | 作用 | 是否自动换行 |
|---|---|---|
| write(s) | 写入字符串s | 否,需手动加 \n |
| writelines(seq) | 写入字符串序列 | 否,需每项自带 \n |
seek()与tell():文件指针控制
withopen('data.txt','r')asf:print(f.tell())# 0,指针在开头f.read(5)# 读5个字符print(f.tell())# 5,指针移到第5个字符后f.seek(0)# 指针回到开头print(f.read(5))# 又读到前5个字符seek(offset, whence):
whence=0(默认):从文件头偏移 offset 字节whence=1:从当前位置偏移whence=2:从文件末尾偏移(offset 通常为负数)
os.path vs pathlib:路径操作两代方案
| 操作 | os.path 写法 | pathlib 写法(推荐) |
|---|---|---|
| 拼接路径 | os.path.join('a', 'b') | Path('a') / 'b' |
| 文件名 | os.path.basename(p) | Path(p).name |
| 无后缀名 | os.path.splitext(p)[0] | Path(p).stem |
| 后缀 | os.path.splitext(p)[1] | Path(p).suffix |
| 父目录 | os.path.dirname(p) | Path(p).parent |
| 是否存在 | os.path.exists(p) | Path(p).exists() |
| 是文件吗 | os.path.isfile(p) | Path(p).is_file() |
| 是目录吗 | os.path.isdir(p) | Path(p).is_dir() |
推荐用 pathlib:它是 Python 3.4+ 引入的面向对象路径方案,
Path('a') / 'b'这种斜杠拼接比os.path.join直观得多,也是现代 Python 的主流选择。
encoding 参数:不乱码的关键
open('file.txt',encoding='utf-8')# 现代文本首选open('file.txt',encoding='gbk')# Windows 中文文本有时用这个不指定 encoding 时,Windows 上默认用 GBK,可能出现乱码。始终显式指定 encoding=‘utf-8’是最佳实践。
2、代码示例
示例1、写文件基础操作
withopen('D:\Desktop\poem.txt','w',encoding='utf-8')asf:f.write('白日依山尽,\n')f.write('黄河入海流。\n')f.write('欲穷千里目,\n')f.write('更上一层楼。\n')print('文件生成:poem')# open('poem.txt', 'w') —— 以写入模式打开,encoding='utf-8' 确保中文不乱码# f.write() —— 每次写一行,注意手动加 \n 换行符# with 块结束,文件自动关闭示例2:读文件的四种方式
#方法一:read()一口气读完(适合小文件)withopen('D:\Desktop\poem.txt','r',encoding='utf-8')asf:all_content=f.read()print('====read()====')print(all_content)#方法二:readline()逐行读withopen('D:\Desktop\poem.txt','r',encoding='utf-8')asf:print('======readline()=====')line=f.readline()whileline:print(f'->{line.strip()}')# strip() 去掉末尾换行符line=f.readline()#方法三:readlines()一次都成列表withopen('D:\Desktop\poem.txt','r',encoding='utf-8')asf:lines=f.readlines()print('=======readlines()=======')print(lines)#['1234\n', '2234\n', '3234\n', '4234\n', '5234']#方案四:直接迭代文件对象(最推荐,内存友好)withopen('D:\Desktop\poem.txt','r',encoding='utf-8')asf:print('=======for line in f========')fori,lineinenumerate(f,1):print(f'第{i}行:{line.strip()}')示例3:追加模式-写日志
importdatetime# 模拟写日志:每次运行追加一行,不会覆盖之前内容log_entry=f'[{datetime.datetime.now().strftime("%H:%M:%S")}]程序启动成功\n'withopen('D:\Desktop\qwe.log','a',encoding='utf-8')asf:f.write(log_entry)print('日志已追加(可多次运行本段验证)')# 'a' 模式:append,每次打开文件指针自动移到末尾# 多次运行后打开 qwe.log 看看,内容一直在增长示例4、seek和tell实操
#创造测试文件withopen('D:\\Desktop\\test\\qweasd.txt','w',encoding='utf-8')asf:f.write('0123456789ABCDEF')withopen('D:\\Desktop\\test\qweasd.txt','r',encoding='utf-8')asf:print(f'初始位置:{f.tell()}')# 0,tell()返回当前指针位置chunk1=f.read(4)print(f'读了4个字符:"{chunk1}",当前指针:{f.tell()}')# 4chunk2=f.read(4)print(f'再读4个字符: "{chunk2}",当前指针:{f.tell()}')# 8f.seek(2)# 指针跳到第2个字符(从0开始)print(f'seek(2)后指针:{f.tell()}')print(f'从位置2开始读6个:“{f.read(6)}”')# "234567"# seek() 就像按遥控器快进/快退到视频的某个时间点示例 5:pathlib 实用操作
frompathlibimportPath#当前文件所在目录desktop=Path(r'D:\Desktop\python学习')print(f'目录是否存在:{desktop.exists()}')print(f'是目录吗:{desktop.is_dir()}')#拼接路径:用斜杠就行log_file=desktop/'app.log'print(f'日志存在路径:{log_file}')print(f'日志文件存在吗:{log_file.exists()}')#分解路径iflog_file.exists():print(f'文件名:{log_file.name}')#app.logprint(f'去掉后缀:{log_file.stem}')#appprint(f'后缀:{log_file.suffix}')#.logprint(f'父目录:{log_file.parent}')#D:\Desktop\python学习print(f'文件大小:{log_file.stat().st_size}字节')# 0 字节#遍历目录下所有。md文件md_files=list(desktop.glob('*.md'))print(f'\n当前目录下,md文件数量{len(md_files)}')forfinmd_files:print(f'{f.name}')示例6:统计代码行数工具
frompathlibimportPathdefcount_lines(directory):total=0blank=0comment=0forpy_fileinPath(directory).rglob('.py'):withopen(py_file,'r',encoding='utf-8')asf:forlineinf:total+=1stripped=line.strip()ifstripped=='':blank+=1elifstripped.startswith('#'):comment+=1code_lines=total-blank-commentprint(f'总行数:{total}')print(f'代码行:{code_lines}')print(f'空行:{blank}')print(f'注释行:{comment}')returntotal,code_lines,blank,comment# 统计当前目录count_lines('.')# rglob('*.py') 递归搜索所有 .py 文件,类似在文件夹里输入 *.py 搜索大文件永远用
for line in f,不要用read()或readlines()。前者内存只存一行,后者会把整个文件吃进内存,几百 MB 的文件会让程序卡死。二进制文件用
'rb'/'wb':图片、视频、PDF 等必须用二进制模式。如果误用文本模式打开图片,Python 会尝试用 encoding 解码二进制数据,导致乱码或报错。写入后立即 flush():
f.flush()强制把缓冲区内容刷到磁盘。在写入关键数据(如金融日志)后调用,防止程序崩溃导致数据丢失。临时文件用 tempfile 模块:
tempfile.NamedTemporaryFile()自动创建临时文件,用完自动删除,避免手动清理。换行符陷阱:Windows 用
\r\n,Linux/macOS 用\n。open()的newline参数可以控制换行的行为,但一般用默认就行,Python 会自动处理跨平台换行。