python文件IO学习

Python 文件 IO

from email.encoders import encode_noop

from fileinput import close

文件 IO 核心概念

文件 IO 就是磁盘文件的读取、写入、追加、修改、遍历、删除操作。

什么是文件 IO

IO = Input (读文件) / Output (写文件)

计算机文件分两类:

文本文件:txt、csv、py、json,字符编码存储(utf-8/gbk),肉眼可读

二进制文件:图片、视频、exe、zip、4G 模组日志 bin,直接字节存储,不能直接用文本打开乱码

Python 文件操作核心入口:内置函数 open(),分为两大读写模式:

文本模式(Text Mode):读写字符串,自动处理换行符编码,.txt/.csv/.log使用;

二进制模式(Binary Mode):读写原始字节bytes,无编码转换,图片、视频、exe、固件、Base64 都用二进制读写。

文件操作标准流程:

打开文件 → 读写操作 → 关闭文件

重点:文件必须关闭!否则会出现文件句柄泄漏、内容写不全、文件被锁定无法删除。

open () 函数完整参数语法

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

file:文件路径(相对路径 / 绝对路径)

相对路径:"test.txt"(和 py 脚本同目录)

绝对路径:"C:/log/device.log"(Windows 斜杠建议用/或\\,避免转义)

mode:打开模式

encoding:文本模式指定编码,Windows 默认 gbk,Linux/mac 默认 utf-8,中文文件强制写 encoding="utf-8"

errors:编码错误处理

errors="ignore":忽略乱码字符(读取破损日志用)

errors="strict":编码错误直接抛异常(默认)

newline:控制换行符转换(文本模式专用,Windows 换行\r\n,Linux\n)

buffering:缓冲区设置,大文件读写调整缓冲区提升性能

mode 打开模式

模式 含义 文件不存在 文件存在 适用场景

r 只读(文本) 报错 FileNotFoundError 从头读取 读取配置、日志

w 只写(文本) 创建新文件 清空全部内容再写入 覆盖写入日志

a 追加写(文本) 创建新文件 在文件末尾新增内容 滚动日志写入

r+ 读写 报错 可读可写,覆盖前部内容 修改文件中部

w+ 读写 创建 直接清空 先清空再读写

a+ 追加读写 创建 只能在末尾写,可读 日志边读边追加

rb 只读二进制 报错 读取字节 图片、视频、固件

wb 覆盖写二进制 创建 清空覆盖 保存二进制数据

ab 追加二进制 创建 尾部追加字节 二进制日志续存

后缀说明:

带b = 二进制模式,不能指定 encoding,读写都是bytes

不带b = 文本模式,必须指定encoding防止乱码

encoding 编码(文本模式专用)

常用:

encoding="utf-8"(通用,Linux、Python 默认)

encoding="gbk"(Windows 记事本默认编码)

不指定编码:Windows 默认 gbk,Linux 默认 utf-8,极易中文乱码。

errors:编码容错

errors="strict" 默认,编码错误直接抛异常

errors="ignore" 忽略无法解码的字节(日志兼容常用)

errors="replace" 乱码字符替换为�

newline:换行符适配

Windows 换行\r\n,Linux\n,设置newline=''可以保留原始换行符,解析日志必备

1. 文件操作核心流程

Python 文件 IO 本质是通过操作系统提供的文件描述符(File Descriptor,FD)与磁盘交互,

拿到f = open(...)后可用方法:

读取类

f.read(n):读取 n 个字符 (文本)/n 字节 (二进制),不传 n 读取整个文件

f.readline():读取一行(包含换行符)

f.readlines():读取所有行,返回列表"行1\\n","行2\\n"

写入类

f.write("字符串"):写入文本,返回写入字符数

f.write(b'\x00\x01'):二进制写入 bytes

f.writelines("第一行","第二行"):批量写入序列,不会自动加换行

指针 & 刷新

f.tell():获取当前文件指针位置(字节偏移)

f.seek(offset, whence):移动文件指针

whence=0:从文件开头偏移

whence=1:从当前位置偏移(二进制模式才支持)

whence=2:从文件末尾偏移

f.flush():强制把缓冲区数据写入磁盘(不关闭文件也落地)

收尾

f.close():关闭文件,释放句柄

核心流程分为3步,每一步都有对应的底层机制,且涉及操作系统内核与用户态的交互:

1.打开文件:调用操作系统 open() 系统调用,内核为文件分配文件描述符(非负整数,如0代表标准输入、1代表标准输出、2代表标准错误)、文件对象(包含读写指针、权限、状态、缓冲区等信息),并将文件描述符返回给用户态。Python 中通过 open() 函数封装此过程,返回的文件对象(如 _io.TextIOWrapper)包含 FD、模式、缓冲区、编码等属性,可通过 fileno() 方法获取文件描述符。

示例:

with open("test.txt", "r") as f:

print(f"文件描述符:{f.fileno()}") # 输出:3(默认前3个FD被标准占用)

2.读写操作:通过文件对象调用 read()/write() 等方法,本质是调用操作系统 read()/write() 系统调用,

数据会先经过内核缓冲区(Page Cache),再同步到磁盘(默认非实时,需手动 flush 或关闭文件触发同步)。

内核缓冲区的作用是减少磁盘 I/O 次数,提升效率,例如多次小写入会被合并为一次磁盘写入。

示例:

with open("test.txt", "w") as f:

f.write("hello") # 数据先写入内核缓冲区,未立即到磁盘

f.flush() # 强制刷新缓冲区,数据同步到磁盘

关闭文件:调用操作系统 close() 系统调用,释放文件描述符、内核资源,清空缓冲区数据并同步到磁盘,

Python 中 close() 方法会触发此操作,with 语句会自动执行 close(),避免资源泄漏。

若未关闭文件,会导致 FD 耗尽(默认单进程可打开文件数约1024,可通过 ulimit -n 查看),

后续文件操作报错。示例:# 错误写法(未关闭文件,资源泄漏)

f = open("test.txt", "w")

f.write("test")

3.# 正确写法(with 自动关闭)

with open("test.txt", "w") as f:

f.write("test")

文件描述符是有限资源(默认单进程可打开文件数约1024),未关闭文件会导致 FD(文件描述符 File Descriptor,FD)耗尽,后

续文件操作报错。可通过以下代码查看当前进程已打开的文件数:

import os

import psutil

pid = os.getpid() # 获取当前进程ID

process = psutil.Process(pid)

print(f"当前进程已打开文件数:{len(process.open_files())}")

2. 缓冲区机制(核心影响读写效率与数据安全)

缓冲区是内存中的临时存储区域,用于减少系统调用次数,提升 IO 效率,分为3种类型,对应 open() 的 buffering 参数,

其底层逻辑是通过内存缓冲降低磁盘访问频率,不同缓冲类型适用于不同场景:

•全缓冲(buffering=-1,默认):缓冲区填满后才触发系统调用,文本模式下缓冲区大小为4096字节(4KB),二进制模式为系统默认(通常4096或8192字节)。

适用于大文件批量读写,效率最高,因为减少了系统调用次数。

示例:# 全缓冲写入,缓冲区填满后才同步到磁盘

with open("big_file.txt", "w", buffering=-1) as f:

for i in range(1000):

f.write(f"line {i}\n") # 多次写入,缓冲区填满后一次性同步

•行缓冲(buffering=1):文本模式下,遇到换行符 \n 即触发缓冲区刷新,同步到磁盘。适用于日志文件实时写入(如程序运行日志,需实时查看最新内容),

确保每一行日志都能及时落地。

示例:# 行缓冲,每写入一行就同步到磁盘

with open("app.log", "a", buffering=1) as f:

f.write("log line 1\n") # 立即同步到磁盘

f.write("log line 2\n") # 立即同步到磁盘

•无缓冲(buffering=0):每次读写操作直接触发系统调用,不经过缓冲区,数据实时同步到磁盘。适用于需要实时同步的关键数据(如配置文件修改、关键日志记录),

但效率最低,不适合大文件,因为每次操作都要访问磁盘。

示例:# 无缓冲,每次写入都直接同步到磁盘

with open("critical_data.txt", "w", buffering=0) as f:

f.write(b"critical data") # 注意:无缓冲时写入需为bytes

实操技巧:手动刷新缓冲区可调用 f.flush() 方法,强制将缓冲区数据同步到磁盘,无需等待缓冲区填满或关闭文件。此外,

可通过 f.buffer 属性访问底层缓冲区对象,查看缓冲区状态(如是否已满)。

示例:with open("test.txt", "w") as f:

f.write("test data")

print(f"缓冲区是否已满:{f.buffer.full()}") # 查看缓冲区状态

f.flush() # 强制刷新

3. 编码与换行符底层处理

3.1 编码转换底层逻辑

文本模式下,Python 会自动对读写数据进行编码/解码:读取时,将磁盘中的字节(bytes)按指定编码解码为字符串(str);

写入时,将字符串按指定编码编码为字节写入磁盘。二进制模式下无编码转换,直接读写原始字节。其底层逻辑是通过 codec 模块实现编码/解码,

不同编码对应不同的字节映射规则(如 UTF-8 用1-4字节表示一个字符,GBK 用2字节表示一个字符)。

示例:# 编码转换示例

text = "中文测试"

utf8_bytes = text.encode("utf-8") # 编码为UTF-8字节:b"\xe4\xb8\xad\xe6\x96\x87\xe6\xb5\x8b\xe8\xaf\x95"

gbk_bytes = text.encode("gbk") # 编码为GBK字节:b"\xd6\xd0\xce\xc4\xb2\xe2\xca\xd4"

print(utf8_bytes)

print(gbk_bytes)

# 解码示例

decoded_text = utf8_bytes.decode("utf-8") # 解码为字符串:"中文测试"

print(decoded_text)

见编码问题:Windows 系统默认编码为 GBK,Linux/macOS 为 UTF-8,若文件编码与代码中指定的 encoding 不一致,

会出现 UnicodeDecodeError 乱码,需精准匹配编码或使用 errors="ignore"/"replace" 处理。

若不确定文件编码,可使用 chardet 库检测(pip install chardet)。示例(检测编码):import chardet

with open("unknown_encoding.txt", "rb") as f:

raw_data = f.read()

result = chardet.detect(raw_data)

print(f"文件编码:{result'encoding'},置信度:{result'confidence'}")

# 根据检测结果读取文件

with open("unknown_encoding.txt", "r", encoding=result'encoding') as f:

content = f.read()

换行符转换机制

不同系统换行符不同:Windows 为 \r\n(CRLF,占2字节),Linux/macOS 为 \n(LF,占1字节),

Python 文本模式下会自动处理换行符:读取时将系统换行符转换为 \n,写入时将 \n 转换为系统对应换行符。

可通过 newline 参数控制,其底层逻辑是在读写时对换行符进行映射转换,避免跨平台文件共享时出现换行符混乱。

示例(跨平台换行符处理):

•newline=None(默认):自动适配系统换行符,读写时自动转换。适用于本地文件操作,无需关心跨平台问题。

示例:# Windows 系统写入,换行符会自动转为 \r\n

with open("test.txt", "w", newline=None) as f:

f.write("line1\nline2")

# 读取时,\r\n 会自动转为 \n

with open("test.txt", "r", newline=None) as f:

print(f.read()) # 输出:line1\nline2

•newline='':不进行换行符转换,读取时保留原始换行符,写入时直接写入 \n。适用于需要保留原始换行符的场景(如跨平台文件传输)。

示例:# 写入时不转换换行符,直接写入 \n

with open("test.txt", "w", newline='') as f:

f.write("line1\nline2")

# 读取时保留原始换行符(Windows 下仍为 \n)

with open("test.txt", "r", newline='') as f:

print(f.read()) # 输出:line1\nline2

•newline='\n'/'\r\n':强制指定换行符,读写时统一使用该换行符,跨平台文件共享时常用,确保文件在不同系统中换行符一致。

示例:# 强制使用 \n 作为换行符,跨平台统一

with open("cross_platform.txt", "w", newline='\n') as f:

f.write("line1\nline2")

# 读取时也强制按 \n 解析

with open("cross_platform.txt", "r", newline='\n') as f:

print(f.read()) # 输出:line1\nline2

基础读写实战(不推荐裸 open,仅演示原理)

案例 1:文本只读 r

读取日志文件

f = open("test.txt",mode="r",encoding="utf-8",errors="ignore")

content = f.readline()

print(content)

f.close()

案例 2:覆盖写入 w

f = open("test.txt","w",encoding="utf-8",errors="ignore")

f.write("test\n")

f.write("test1")

f.close()

案例 3:追加写入 a

f = open("test.txt","a",encoding="utf-8",errors="ignore")

f.write("\ntest3\n")

f.write("test4")

f.close()

案例 4:二进制读写(图片 / 固件 / 模组 bin 日志)

读取图片 rb:Read Binary 二进制只读

f = open("123.png","rb")

img_bytes = f.read()

print(img_bytes)

f.close()

复制图片 wb:Write Binary 二进制覆盖写入 新建 / 覆盖文件写入二进制流,文件不存在则创建,存在直接清空重写。

ab = 二进制追加写入

f_out = open("123_copy.png","wb")

f_out.write(img_bytes)

f_out.close()

案例 5:按行读取大文件(避免一次性加载内存溢出)

f = open("test.txt", "r", encoding="utf-8")

for line in f: # 迭代器,一行一行加载,内存友好

if "test" in line:

print("存在test:", line.strip())

f.close()

with 上下文管理器 替代手动 close

为什么要用 with?

自动关闭文件:即使代码报错、异常崩溃,也会执行 close (),杜绝句柄泄漏

代码简洁,不用手动写f.close()

基础语法

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

print(f.read())

代码离开with代码块,文件自动关闭

with 追加写入

def with_add_text(text:str):

with open("test.txt", "a", encoding="utf-8") as f:

f.write(f"{text}\n")

with_add_text("123")

with_add_text("456")

同时打开两个文件(读取 + 写入)

with open("test.txt", "r", encoding="utf-8") as f, open("test1.txt", "a", encoding="utf-8") as g:

for line in f:

g.write(f"{line}")

文件指针 seek /tell 进阶(修改文件、截断)

文件是线性数据流,读写共用一个指针,写会覆盖当前指针位置内容。

w+模式:打开文件清空原有内容,可读可写;w 只能写,调用f.read()直接报错

当你传入 encoding 参数时,Python 类型检查器(Pyright / Pylance / VSCode 类型提示)强制要求:

文件打开模式必须是文本模式,不能只写w+(类型识别歧义)。

w+ 同时兼容文本 / 二进制,类型推断模糊,加上encoding参数直接触发类型警告:

Unexpected type(s): (str, Literal"w+", None)

w+ 简写,类型工具分不清是文本wt+还是二进制wb+,可以不屑encoding

with open("test.txt", "w+") as f:

f.write("hello world")

print(f.tell())

f.seek(0) # 指针跳回文件开头

print(f.read())

f.seek(2)

print(f.read(3))

截断文件(清空文件后半段)

with open("test.txt", "r+", encoding="utf-8") as f:

f.seek(2)

print(f.read()) # f.read() 读完所有内容,文件指针跑到文件最后,truncate() 直接把文件截到末尾,等于没截断效果。

f.truncate() # 从指针位置截断,后面内容全部删除

# print(f.read())

需要先截断,再读,不能先read

with open("test.txt", "r+", encoding="utf-8") as f:

f.seek(2)

f.truncate() # 直接在位置2截断,文件只剩 AB

f.seek(0)

print(f.read()) # AB

:truncate 带参数用法(固定截断长度)

f.truncate(size):直接把文件强制裁剪为 size 字节,无视当前指针

with open("test.txt", "r+", encoding="utf-8") as f:

f.truncate(3) # 文件只保留前3字节

路径处理:os.path vs pathlib(推荐 pathlib,Python3.4+)

手动拼接路径极易出错,跨 Windows/Linux 兼容用路径库。

from pathlib import Path

# 定义文件路径

log_path = Path("./modem/modem.log")

# 父目录

print(log_path.parent)

# 判断文件是否存在

print(log_path.exists())

# 创建父目录(不存在则创建)

log_path.parent.mkdir(parents=True, exist_ok=True)

# 直接打开文件,无需open

with log_path.open("a", encoding="utf-8") as f:

f.write("日志内容\n")

# 获取绝对路径

print(log_path.absolute())

# 文件名、后缀

print(log_path.name, log_path.suffix)

os.path(传统写法,老项目兼容)

import os

file_path = os.path.join("modem", "modem.log") # 自动适配斜杠

print(os.path.exists(file_path))

print(os.path.dirname(file_path)) # 目录

os.makedirs(os.path.dirname(file_path), exist_ok=True)

文件 IO 异常处理

文件操作高频异常:

FileNotFoundError:文件不存在(r 模式)

PermissionError:权限不足(Linux /dev/radio_cmd 典型报错)

UnicodeDecodeError:文本编码不匹配乱码报错

OSError:硬件设备文件异常

带异常捕获的写入函数

from pathlib import Path

def safe_write_log(file_path: Path, msg: str):

try:

# 确保文件夹存在

file_path.parent.mkdir(parents=True, exist_ok=True)

with file_path.open("a", encoding="utf-8", errors="ignore") as f:

f.write(f"{msg}\n")

f.flush() # 强制落盘,防止断电丢失日志

print(f"写入成功")

except PermissionError:

print(f"错误:文件{file_path}权限不足,无法写入")

except OSError as e:

print(f"系统文件异常:{e}")

except Exception as e:

print(f"未知异常:{type(e).name}: {e}")

# 测试写入模组日志

log_file = Path("./modem.log")

print("日志完整路径:", log_file.resolve())

safe_write_log(log_file, "MODEM modem_current_state:1")

safe_write_log(log_file, "MODEM open interface /dev/radio_cmd failed.")

from pathlib import Path

def parse_modem_log(log_file: Path):

error_state = {1: "模组初始化失败", 19: "拨号PDP激活失败"}

result = \[\]

with log_file.open("r", encoding="utf-8", errors="ignore") as f:

for line in f:

line = line.strip()

if "modem_current_state:" in line:

state_code = int(line.split(":")-1)

desc = error_state.get(state_code, "未知状态")

result.append((line, state_code, desc))

return result

# 使用

logs = parse_modem_log(Path("modem.log"))

for item in logs:

print(f"原始日志:{item0} | 状态码:{item1} | 说明:{item2}")

二进制文件批量复制

from pathlib import Path

def copy_bin_file(src: Path, dst: Path):

if not src.exists():

print("源文件不存在")

return

# 二进制块读写,防止大文件内存爆满

chunk_size = 4096

with src.open("rb") as fr, dst.open("wb") as fw:

while chunk := fr.read(chunk_size):

fw.write(chunk)

copy_bin_file(Path("firmware.bin"), Path("firmware_backup.bin"))

清空文件内容

方式1

with open("clear.log", "w", encoding="utf-8") as f:

pass

# 方式2 truncate

with open("clear.log", "r+", encoding="utf-8") as f:

f.truncate(0)

相关推荐
丁引1 小时前
《数据清洗的艺术:如何用20行核心逻辑优雅地删除无标签图片》
前端·数据库·python
梦想的旅途21 小时前
企业微信API二次开发:外部群模块功能清单与全场景对接
java·python·企业微信
0566461 小时前
Python高级——迭代器
开发语言·python·学习
老白干2 小时前
基于 Spring AOP 的操作日志记录:以 DeptController 增删接口为例
java·python·spring
John jj2 小时前
拆解 Telegram 群组频道收录市场:三类方案,一个可运行的评分模型,目前TG中文人工评分加模型评分机制——LetsTG收录“快速”、“无门槛”
大数据·后端·python·深度学习·搜索引擎·django·全文检索
aqi002 小时前
15天学会AI应用开发(十九)使用LangGraph实现持久记忆功能
人工智能·python·大模型·ai编程·ai应用
月光船幽幽2 小时前
KDTree查表实现观测到动作的统计映射
python
2401_868534782 小时前
论信息系统安全保障规划与设计
python·django