【Python】文件处理(一)
目录
- 文件处理
- 基本概念
- 打开文件
- 写入文件
- 读取文件
- 关闭文件
- 文件指针
- 文件缓冲
- 重命名和删除文件
- 移动和复制文件
- 目录操作(文件夹管理)
文件处理
基本概念
在 Python 中,文件处理就是和计算机磁盘上的文件进行交互:
- 打开文件(open)
- 读取数据(read)
- 写入数据(write)
- 关闭文件(close)
Python 提供了内置函数 open()
来完成文件的打开操作,返回一个 文件对象,我们就可以通过这个对象进行读写。
语法:
file = open("filename", "mode")
"filename"
:文件路径,可以是相对路径(example.txt
)或绝对路径(/Users/.../example.txt
)"mode"
:打开文件的模式(如r
读,w
写,a
追加,rb
二进制读等)
打开文件
在 Python 中,文件操作分为 三个阶段:
- 打开文件(open):建立程序与文件之间的连接,返回一个文件对象(file object)。
- 操作文件(读写):通过文件对象的方法(如
.read()
、.write()
)来读写文件内容。 - 关闭文件(close):断开连接,释放资源。
第一步“打开文件”就靠内置函数 open()
。
open() 函数语法
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
参数详解:
参数 | 作用 |
---|---|
file | 文件路径,可以是相对路径或绝对路径,例如 "test.txt" 或 "C:/Users/me/test.txt" |
mode | 文件打开模式(默认 'r' ,只读模式),详细见下一节 |
buffering | 缓冲策略: 0 = 无缓冲(只能二进制模式) 1 = 行缓冲(文本模式常用) >1 = 使用指定大小的缓冲区 -1 = 系统默认 |
encoding | 指定编码方式(常用 'utf-8' ,Windows 下如果不指定可能默认 'cp936' /GBK,容易乱码) |
errors | 出现编码错误时的处理方式,比如 'ignore' (忽略)、'replace' (替换为 ? ) |
newline | 控制换行符的处理(文本模式下才生效),常用于跨平台文件换行一致性 |
closefd | 如果为 False ,则文件描述符不会被关闭(一般不用,特殊场景才设置) |
opener | 高级用法,可以自定义打开逻辑(例如沙盒限制、加密文件等) |
文件打开模式(mode)
模式参数最常用的是以下几种(必须理解清楚):
模式 | 含义 | 文件不存在时 | 文件存在时 |
---|---|---|---|
'r' | 只读模式(默认) | 报错 | 从头开始读取 |
'w' | 写入模式(覆盖写) | 创建新文件 | 清空原文件再写 |
'a' | 追加模式(append) | 创建新文件 | 在文件末尾写入 |
'x' | 独占写模式 | 创建新文件 | 报错,避免覆盖 |
'b' | 二进制模式 | - | - |
't' | 文本模式(默认) | - | - |
'+' | 读写模式(更新模式) | - | - |
常见组合:
'rb'
:以二进制方式只读文件(读图片、视频、音频等)'wb'
:以二进制方式写文件(覆盖)'a+'
:追加并可读写'r+'
:可读写(从文件头开始,不清空内容)'w+'
:可读写,但会先清空文件
打开文件的基本示例
-
只读模式
# 打开文件(默认就是 'r') f = open("example.txt", "r", encoding="utf-8") content = f.read() print(content) f.close()
-
写入模式(覆盖)
f = open("example.txt", "w", encoding="utf-8") f.write("Hello, Python!\n") f.close()
-
追加模式
f = open("example.txt", "a", encoding="utf-8") f.write("This line will be added.\n") f.close()
-
二进制模式
f = open("image.png", "rb") data = f.read() print(len(data)) # 打印字节数 f.close()
推荐用法:with 上下文管理器
好处:不用手动调用 close()
,Python 会自动关闭文件,避免资源泄露。
with open("example.txt", "r", encoding="utf-8") as f:content = f.read()print(content)# 退出 with 语句块时,文件自动关闭
常见错误与注意事项
-
编码问题(Windows 上最常见)
# 可能报错:UnicodeDecodeError f = open("test.txt", "r") # 推荐:明确指定 utf-8 f = open("test.txt", "r", encoding="utf-8")
-
模式错误
- 文件不存在时用
'r'
打开 →FileNotFoundError
- 用
'w'
打开时会清空原内容 → 小心数据丢失 - 想读写都能用时不要忘记加
'+'
- 文件不存在时用
-
路径问题
- Windows 下路径要么用
r"C:\Users\me\test.txt"
(原始字符串) - 要么用斜杠
/
(推荐,Python 能识别)
- Windows 下路径要么用
-
二进制与文本模式区别
- 文本模式下,内容是
str
- 二进制模式下,内容是
bytes
- 文本模式下,内容是
写入文件
文件写入指的是把程序中的数据(字符串或字节)保存到磁盘文件中。
在 Python 中,写文件通常有三步:
- 打开文件(用
open()
并选择写模式w
、追加模式a
、或读写模式+
)。 - 写入内容(常用
.write()
或.writelines()
)。 - 关闭文件(
.close()
或用with
自动关闭)。
常用写入方法
-
write(string)
- 将一个 字符串 写入文件。
- 返回写入的字符数(文本模式)或字节数(二进制模式)。
with open("example.txt", "w", encoding="utf-8") as f:count = f.write("Hello, Python!\n")print("写入了字符数:", count) # 输出 15
-
writelines(iterable)
- 一次性写入 可迭代对象(如列表、元组)中的多个字符串。
- 不会自动加换行符,需要自己在字符串里加
\n
。
lines = ["第一行\n", "第二行\n", "第三行\n"] with open("example.txt", "w", encoding="utf-8") as f:f.writelines(lines)
-
print(..., file=f)
- 利用
print()
的file
参数写入,比.write()
更方便(会自动换行,除非指定end=""
)。
with open("example.txt", "w", encoding="utf-8") as f:print("使用 print 写入一行", file=f)print("自动带换行", file=f)
- 利用
文件写入模式
写入时必须选择正确的模式,否则可能导致数据丢失:
模式 | 含义 | 文件不存在 | 文件存在 |
---|---|---|---|
'w' | 只写模式 | 新建文件 | 清空原内容 |
'a' | 追加模式 | 新建文件 | 在文件末尾追加 |
'x' | 独占写 | 新建文件 | 文件已存在时报错 |
'w+' | 读写模式 | 新建文件 | 清空原内容 |
'a+' | 追加读写 | 新建文件 | 从末尾追加,但可读 |
小结:
- 覆盖写 →
'w'
、'w+'
- 追加写 →
'a'
、'a+'
- 安全写(不想覆盖已有文件) →
'x'
代码示例
-
覆盖写入
with open("example.txt", "w", encoding="utf-8") as f:f.write("这会清空旧文件并写入新内容\n")
-
追加写入
with open("example.txt", "a", encoding="utf-8") as f:f.write("这是追加的一行\n")
-
写入多行(列表)
lines = ["line1\n", "line2\n", "line3\n"] with open("example.txt", "w", encoding="utf-8") as f:f.writelines(lines)
-
二进制写入
- 用于写图片、音频等非文本数据。
data = b"\x48\x65\x6c\x6c\x6f" # "Hello" 的字节形式 with open("binary.dat", "wb") as f:f.write(data)
-
边读边写(更新模式)
with open("example.txt", "r+", encoding="utf-8") as f:f.write("更新文件开头内容")f.seek(0) # 移动到文件头print(f.read()) # 读出全部内容
常见注意事项
-
编码问题
- 写入中文时建议指定
encoding="utf-8"
,否则可能乱码。
with open("example.txt", "w", encoding="utf-8") as f:f.write("你好,世界\n")
- 写入中文时建议指定
-
换行符问题
write()
不会自动换行,需要\n
。print(..., file=f)
会自动加换行。
-
数据未立即写入磁盘
- Python 会使用缓冲区,文件内容可能暂存在内存。
- 解决:
f.flush()
→ 手动刷新缓冲区。with open(...)
→ 自动关闭文件时刷新。
-
覆盖风险
'w'
或'w+'
会清空文件,小心数据丢失。- 如果只想追加,应使用
'a'
或'a+'
。
-
写入二进制 vs 文本
- 文本模式下
.write()
参数必须是str
。 - 二进制模式下
.write()
参数必须是bytes
。
- 文本模式下
读取文件
文件读取的目标是从磁盘中获取数据到内存。
在 Python 中,读取通常步骤:
- 打开文件(
open()
,模式一般是'r'
、'rb'
或'r+'
)。 - 读取文件内容(用
.read()
、.readline()
、.readlines()
或迭代)。 - 关闭文件(
f.close()
或with open(...)
自动关闭)。
常见读取方法
-
read([size])
- 作用:读取文件中指定字节/字符数;如果不传
size
,则读取整个文件。 - 返回值:字符串(文本模式)或字节串(字节模式)。
with open("example.txt", "r", encoding="utf-8") as f:data = f.read() # 一次性读完整个文件print(data)with open("example.txt", "r", encoding="utf-8") as f:part = f.read(10) # 只读前10个字符print(part)
大文件时不建议一次性读完,容易占满内存,应分块读。
- 作用:读取文件中指定字节/字符数;如果不传
-
readline([size])
- 作用:读取文件中的一行(包含换行符
\n
),读到末尾返回空字符串。 - 参数
size
:可选,表示最多读多少字符,不一定能读完整一行。
with open("example.txt", "r", encoding="utf-8") as f:line1 = f.readline()print("第一行:", line1)line2 = f.readline()print("第二行:", line2)
- 作用:读取文件中的一行(包含换行符
-
readlines()
- 作用:一次性读取所有行,返回一个 列表,每行作为一个字符串元素。
with open("example.txt", "r", encoding="utf-8") as f:lines = f.readlines()print(lines) # ["第一行\n", "第二行\n", "第三行\n"]
注意:大文件慎用,会把所有行都读入内存。
-
直接迭代文件对象
- 文件对象本身就是可迭代的,可以一行一行读取,更节省内存。
with open("example.txt", "r", encoding="utf-8") as f:for line in f:print("读取到:", line.strip())
推荐大多数情况下用这种方式。
高级读取技巧
-
分块读取(适合大文件)
with open("bigfile.txt", "r", encoding="utf-8") as f:while True:chunk = f.read(1024) # 每次读 1024 字符if not chunk:breakprint(chunk, end="")
-
随机访问文件内容
seek(offset, whence)
:移动文件指针。whence=0
:从开头算(默认)。whence=1
:从当前位置算。whence=2
:从文件末尾算。
tell()
:返回当前位置。
with open("example.txt", "r", encoding="utf-8") as f:f.seek(5) # 移动到第 5 个字符print(f.read(10)) # 读取10个字符print("当前位置:", f.tell())
-
二进制读取
用于图片、音频、视频等非文本文件:
with open("image.png", "rb") as f:data = f.read(20) # 读取前20字节print(data) # b'\x89PNG\r\n\x1a\n...'
-
readinto(buffer)
- 直接读取数据到现有的 字节数组,避免创建新对象(效率更高)。
import array buf = array.array('b', b'\0' * 10) with open("example.txt", "rb") as f:f.readinto(buf) print(buf)
-
内存映射(大文件优化)
- 使用
mmap
可以把文件映射到内存,像操作字节数组一样读写,非常高效。
import mmapwith open("example.txt", "r+b") as f:mm = mmap.mmap(f.fileno(), 0)print(mm[:10]) # 前10个字节mm.close()
- 使用
注意事项与常见问题
- 编码问题
- 默认编码可能不同(Windows 常见
gbk
),建议总是加encoding="utf-8"
。 - 否则可能
UnicodeDecodeError
。
- 默认编码可能不同(Windows 常见
- 换行符差异
- Windows:
\r\n
- Linux / Mac:
\n
- Python 会自动统一为
\n
,除非newline
参数另设。
- Windows:
- 大文件内存占用
- 避免用
.read()
或.readlines()
一次性读入。 - 推荐用 迭代 或 分块读取。
- 避免用
- 二进制 vs 文本
- 文本模式返回
str
。 - 二进制模式返回
bytes
。
- 文本模式返回
- 文件未关闭
- 用
with open(...)
自动关闭,避免资源泄漏。
- 用
关闭文件
为什么要关闭文件?
当我们用 open()
打开一个文件时,操作系统会给程序分配一个 文件描述符(file descriptor),这是系统级别的资源。
如果不关闭文件,可能会导致:
- 资源泄漏:打开太多文件却没关闭,系统会报错:
OSError: [Errno 24] Too many open files
。 - 数据丢失:写入数据时,内容会先存到 缓冲区,如果不关闭或
flush()
,数据可能没写入磁盘。 - 文件锁未释放:某些系统下文件可能被锁定,导致其他程序不能访问。
所以,关闭文件是文件操作必不可少的一步。
close()
方法
最基本的关闭方式是手动调用:
f = open("example.txt", "w", encoding="utf-8")
f.write("Hello, world!\n")
f.close() # 关闭文件,释放资源
检查文件是否关闭
f = open("example.txt", "r", encoding="utf-8")
print(f.closed) # False
f.close()
print(f.closed) # True
.closed
属性可以判断文件是否已经关闭。
with
上下文管理器
Python 推荐使用 with 语句,它会在代码块执行完后自动调用 close()
,即使发生异常也会关闭文件。
with open("example.txt", "w", encoding="utf-8") as f:f.write("这行文字会自动保存\n")# 不需要手动 f.close()
等价写法
下面两种写法效果相同:
# 传统写法
f = open("example.txt", "w", encoding="utf-8")
try:f.write("Hello\n")
finally:f.close()# with 写法(简洁、安全)
with open("example.txt", "w", encoding="utf-8") as f:f.write("Hello\n")
with 的底层原理——上下文管理协议
任何对象只要实现了 __enter__()
和 __exit__()
方法,就满足「上下文管理协议」,可以被 with
语句使用。这两个方法的作用如下:
-
__enter__()
方法-
调用时机:进入
with
代码块时自动调用。 -
作用:通常用于「准备资源」(如打开文件、建立数据库连接、获取锁等)。
-
返回值:会被
with ... as 变量
中的「变量」接收(可以是对象本身,也可以是其他资源对象)。
-
-
__exit__()
方法-
调用时机:退出
with
代码块时自动调用(无论代码块内是否发生异常,一定会执行)。 -
作用:通常用于「清理资源」(如关闭文件、断开数据库连接、释放锁等)。
- 参数:接收三个与异常相关的参数(如果代码块内无异常,这三个参数均为
None
):exc_type
:异常类型(如ValueError
)。exc_value
:异常实例(具体错误信息)。traceback
:异常的追踪信息。
- 参数:接收三个与异常相关的参数(如果代码块内无异常,这三个参数均为
-
返回值:如果返回
True
,则会「抑制异常」(异常不会向外传播);如果返回False
或不返回,则异常会正常抛出。
-
with
语句的执行流程
结合一个实例来看 with
的完整执行步骤:
class Demo:def __enter__(self):print("进入 with 块:准备资源")return self # 返回值会被 as 后的变量接收def __exit__(self, exc_type, exc_value, traceback):print("退出 with 块:清理资源")# 若返回 True,则抑制异常return False# 执行 with 语句
with Demo() as d:print("执行 with 块内的逻辑")# 假设这里可能发生异常# raise ValueError("故意抛出一个异常")
执行流程拆解:
- 创建
Demo
实例(Demo()
)。 - 自动调用实例的
__enter__()
方法,打印「进入 with 块:准备资源」,并将返回值(self
)赋值给d
(即d
就是Demo
实例)。 - 执行
with
块内的代码:打印「执行 with 块内的逻辑」。 - 退出
with
块时,自动调用__exit__()
方法,打印「退出 with 块:清理资源」。
即使块内发生异常(比如取消注释 raise
语句),__exit__()
依然会执行,确保资源被清理。
文件对象正是实现了这套机制,所以 with open(...) as f:
才能自动关闭。
注意事项
-
重复关闭文件
.close()
调用多次不会报错,Python 会忽略。
f = open("example.txt", "r") f.close() f.close() # 没问题
-
关闭后的操作
- 如果对已经关闭的文件进行读写操作,会报
ValueError: I/O operation on closed file
。
f = open("example.txt", "r") f.close() f.read() # 报错
- 如果对已经关闭的文件进行读写操作,会报
-
flush vs close
f.flush()
:只是把缓冲区数据写入磁盘,但文件依然处于打开状态。f.close()
:会先 flush,再释放资源。
-
with 的多文件写法
with open("file1.txt", "r") as f1, open("file2.txt", "w") as f2:for line in f1:f2.write(line)
文件指针
什么是文件指针?
文件指针是操作系统为每个打开的文件维护的 当前位置标记,它表示下一次读或写操作从文件的哪一位置开始。
- 当你打开一个文件时,文件指针默认位置:
'r'
、'r+'
、'w+'
→ 默认在 开头'a'
、'a+'
→ 默认在 末尾
- 读写文件时,文件指针会随操作移动:
- 读操作 → 文件指针向前移动已读取的字节数
- 写操作 → 文件指针向前移动已写入的字节数
文件指针方法
-
tell()
返回当前文件指针位置(以字节为单位)
with open("example.txt", "r", encoding="utf-8") as f:print(f.tell()) # 0f.read(5)print(f.tell()) # 读取 5 个字符后指针位置变为 5(如果是 UTF-8 多字节可能略有差异)
-
seek(offset, whence=0)
移动文件指针到指定位置:
offset
:偏移量(单位:字节)whence
(起点,默认 0):0
:文件开头1
:当前位置2
:文件末尾
示例:
with open("example.txt", "r", encoding="utf-8") as f:f.seek(5) # 从开头向后移动 5 个字节print(f.read(5))f.seek(0) # 回到开头print(f.read(5))f.seek(-5, 2) # 从文件末尾向前 5 个字节print(f.read())
注意:在文本模式下 (
'r'
、'w'
) 偏移量是以字符为单位,如果涉及多字节字符(如中文 UTF-8),seek 有可能出现偏差。
推荐文本文件用'utf-8'
编码读写,如果需要精确字节控制可用二进制模式'rb'
或'wb'
。
文件指针在读写中的应用
-
读文件的随机访问
with open("example.txt", "r", encoding="utf-8") as f:f.seek(10) # 跳过前 10 个字符content = f.read(20) # 读取 20 个字符print(content)
-
写文件的随机插入(覆盖)
with open("example.txt", "r+", encoding="utf-8") as f:f.seek(5)f.write("INSERT") # 会从第 5 个字符开始覆盖原文件内容
-
追加文件内容
with open("example.txt", "a", encoding="utf-8") as f:f.write("\n追加内容")
注意:追加模式下,文件指针会自动移动到文件末尾,seek 修改位置可能不会生效(取决于操作系统实现)。
为什么文本模式下 seek()
容易出问题?
- UTF-8/UTF-16 等编码的多字节特性
- 英文字母通常占 1 个字节
- 中文、日文、韩文等常占 3 个字节(UTF-8)或 2 个字节(UTF-16)
seek(n)
只移动 字节,而不是 字符- 如果偏移量落在一个字符的中间,就会导致
read()
出现乱码或UnicodeDecodeError
- Python 的文本模式解码机制
- 在
"r"
模式下,Python 会自动解码字节 → 字符 - 如果字节流被切割,解码器无法识别完整字符
- 在
安全策略
-
使用二进制模式 (
rb
/wb
)如果需要精确的字节控制,先在二进制模式下用
seek()
定位,再手动解码:with open("test.txt", "rb") as f:f.seek(6) # 跳过前 6 个字节data = f.read(6) # 读取 6 个字节print(data.decode("utf-8", errors="ignore"))
优点:精确控制字节偏移
缺点:需要自己处理编码 -
通过字符计数定位
先按 字符 读取,再用
len(encode)
计算对应的字节数,然后再seek()
:text = "你好Python" # 想跳到第3个字符(P) prefix = text[:2] # 前两个字符 "你好" byte_offset = len(prefix.encode("utf-8"))with open("test.txt", "rb") as f:f.seek(byte_offset)print(f.read().decode("utf-8")) # 输出 "Python"
思路:把字符串切片转成字节长度,保证不会切到字符中间
-
逐行读取 + 内存切片
如果只需要定位某一行里的某个字符,可以用字符串切片代替
seek()
:with open("test.txt", "r", encoding="utf-8") as f:line = f.readline() # 读取一行print(line[2:]) # 从第3个字符开始
适合逐行处理,不依赖文件指针字节位置
-
借助
io
模块的TextIOWrapper
TextIOWrapper
提供了 按字符缓冲 的文件流,可以在二进制和文本模式之间切换:import iowith open("test.txt", "rb") as raw:f = io.TextIOWrapper(raw, encoding="utf-8")f.seek(0, 0) # 移动到底层二进制流的位置print(f.read()) # 自动解码
适合需要在 字节级定位 和 字符级读取 之间切换的场景
底层原理
- 文件在操作系统层面由 文件描述符 表示。
- 文件指针是操作系统维护的一个偏移量,用来标记下一次读写的位置。
- Python 的
file
对象封装了这个文件描述符,并提供.seek()
和.tell()
方法来操作。 - 文件读写实际上是:
read()
→ 从指针位置读取指定字节 → 指针向后移动write()
→ 从指针位置写入数据 → 指针向后移动
注意事项
- 文本模式 vs 二进制模式
- 文本模式 (
'r'
、'w'
):seek/tell 以字符为单位,多字节字符可能有偏差 - 二进制模式 (
'rb'
、'wb'
):seek/tell 精确到字节,适合文件偏移计算
- 文本模式 (
- 追加模式的限制
'a'
/'a+'
模式写入总是追加到末尾,seek 可能不能改变写入位置
- 与缓冲区的关系
- 文件指针指向的位置是 Python 内存缓冲区的位置,而不是硬盘位置
flush()
或close()
会将缓冲区内容写入硬盘
文件缓冲
什么是缓冲区?
当你用 Python 打开文件进行读写时,数据不会直接“一次一字节”与硬盘交互,而是先进入内存中的一个临时区域,这个区域就是 缓冲区(buffer)。
- 目的:减少与磁盘频繁交互,提高性能
- 工作原理:
- 写文件时:数据先写入缓冲区,等缓冲区满了或文件关闭时,再一次性写入磁盘
- 读文件时:一次性从磁盘读取一块内容到缓冲区,程序按需从缓冲区获取
缓冲区的多层结构
当我们在 Python 里 open()
一个文件时,实际涉及 三层缓冲:
- 操作系统缓冲(Page Cache)
- 所有磁盘 I/O 操作首先会经过 OS 缓存(内核页缓存)。
- 程序写入的数据 → 进入 OS 缓存 → 系统稍后再写入硬盘。
- C 标准库缓冲(stdio 层)
- Python 内部调用 C 标准库的
fopen
、fwrite
等,这一层也有缓冲。 - 决定了
buffering
参数的行为(行缓冲 / 全缓冲 / 无缓冲)。
- Python 内部调用 C 标准库的
- Python 层缓冲(io 模块)
- Python
io.TextIOWrapper
(文本模式)和io.BufferedWriter
(二进制模式)再维护一层缓冲。 - 例如:
f.write("abc")
并不会马上写盘,而是写到 Python 的缓冲对象里。
- Python
结论:写入磁盘并不是立刻发生的,而是可能停留在 Python → C stdio → OS 页缓存 的任意一层。
Python 文件缓冲机制
在 open(file, mode, buffering, ...)
中,buffering
参数决定缓冲方式:
buffering 值 | 行为 |
---|---|
0 | 无缓冲(只允许二进制模式)。读写直接操作系统调用,效率低,但实时性强 |
1 | 行缓冲(仅文本模式)。遇到换行符 \n 或调用 flush() /close() 时写入磁盘 |
>1 | 使用指定大小的 固定缓冲区(单位:字节) |
默认 | 文本模式下 → 行缓冲;二进制模式下 → 固定缓冲区(通常 4KB 或 8KB,依操作系统而定) |
flush()
的作用
flush()
会把 Python 层缓冲 和 C stdio 缓冲 的数据推送到 操作系统缓冲。- 但它不能保证数据立刻写入物理磁盘。
示例:
with open("log.txt", "w", encoding="utf-8") as f:f.write("第一条日志\n")f.flush() # 此时数据进入 OS 缓冲,但可能还没写到磁盘
os.fsync()
的作用
如果你要确保数据真正落到磁盘(防止断电丢失),需要调用 系统调用 fsync()
:
import oswith open("log.txt", "w", encoding="utf-8") as f:f.write("关键日志\n")f.flush() # 刷到操作系统缓存os.fsync(f.fileno()) # 强制 OS 把缓存写入硬盘
注意:
fsync()
比flush()
慢得多(需要物理磁盘操作)。- 用在关键场景(数据库写 WAL、金融交易日志),而不是每次写入。
自动刷新机制
Python 有几种方式可以实现“写了就刷新”:
-
行缓冲(
buffering=1
)- 仅文本模式有效。
- 每次写入 换行符
\n
时,缓冲区会自动刷新。
f = open("log.txt", "w", buffering=1, encoding="utf-8") f.write("这一行会立即写入\n") # 遇到换行 -> 自动 flush
-
print(..., flush=True)
Python 的
print
内置参数flush=True
可以强制刷新:print("即时日志", file=open("log.txt", "w"), flush=True)
-
无缓冲(
buffering=0
)- 只允许二进制模式 (
"wb"
、"rb"
)。 - 每次写操作都会直接调用系统写入,实时性最高,性能最差。
f = open("raw.bin", "wb", buffering=0) f.write(b"data") # 立即写入磁盘
- 只允许二进制模式 (
写入效率优化
-
批量写入 vs 逐条写入
-
低效:
with open("data.txt", "w", encoding="utf-8") as f:for i in range(1000000):f.write(f"{i}\n") # 每次写入都可能触发缓冲处理
-
高效:
with open("data.txt", "w", encoding="utf-8") as f:lines = [f"{i}\n" for i in range(1000000)]f.writelines(lines) # 一次性写入
-
-
调整缓冲区大小
f = open("bigfile.txt", "wb", buffering=1024*1024) # 1MB 缓冲区
大文件 I/O 时减少磁盘调用次数,性能显著提升。
-
避免频繁 flush/fsync
-
flush()
/fsync()
都会打断批量写入的优化,过于频繁会拖慢性能。 -
最佳实践:批量写入 → 定时 flush → 关键数据点 fsync。
-
并发与缓冲
在多进程/多线程环境中:
- 缓冲会导致写入顺序不一致,因为每个进程有独立缓冲。
- 如果多个进程同时写文件,可能发生覆盖或交错。
解决方法:
- 使用
O_APPEND
模式(在 Linux 下保证原子追加)。 - 使用 文件锁(
fcntl
或portalocker
)。 - 对日志类写入,推荐:一进程写,其他进程用队列传数据。
重命名和删除文件
文件重命名(Renaming Files)
在 Python 中,主要通过 os
模块 提供的 os.rename()
或 os.replace()
来重命名文件。
-
os.rename(src, dst)
-
作用:把文件(或目录)从
src
路径重命名为dst
路径。 -
语法:
os.rename(src, dst)
src
:原文件路径(字符串)dst
:目标文件路径(字符串)
-
特点:
- 如果
dst
已经存在,Windows 上会报错,Linux 上会覆盖(取决于系统)。 src
和dst
可以在 同一个目录下(单纯改名字),也可以是 不同目录(相当于移动文件)。
- 如果
-
示例:
import os# 假设当前目录下有 old.txt 文件 os.rename("old.txt", "new.txt") # 将 old.txt 重命名为 new.txt print("重命名完成!")
-
-
os.replace(src, dst)
-
作用:与
os.rename()
类似,但更安全,即使目标文件存在也会强制覆盖。 -
语法:
os.replace(src, dst)
-
示例:
import os# 即使 new.txt 已经存在,也会被覆盖 os.replace("temp.txt", "new.txt")
-
区别总结:
方法 目标文件存在时的行为 适用场景 os.rename()
Windows 上报错,Linux 上可能覆盖 普通重命名 os.replace()
强制覆盖 确定要替换旧文件时
-
-
使用
pathlib.Path.rename()
-
Python3 推荐的 面向对象写法:
from pathlib import Pathp = Path("old.txt") p.rename("new.txt") # 等价于 os.rename()
-
注意事项
- 路径正确性
- 如果文件不在当前目录,需要写绝对路径或相对路径。
- 推荐使用
os.path.join()
或Path
来拼接路径。
- 权限问题
- 如果文件被占用(Windows)或没有权限,会抛出
PermissionError
。
- 如果文件被占用(Windows)或没有权限,会抛出
- 跨磁盘/分区限制
- 有些系统
rename
不能跨分区移动文件,需改用shutil.move()
。
- 有些系统
文件删除(Deleting Files)
在 Python 中,可以通过 os.remove()
、os.unlink()
或 Path.unlink()
删除文件。
-
os.remove(path)
-
作用:删除指定路径下的文件。
-
语法:
os.remove(path)
-
示例:
import osos.remove("new.txt") # 删除文件 print("删除完成!")
-
-
os.unlink(path)
-
作用:
os.unlink()
是os.remove()
的别名,两者完全一样。 -
语法:
os.unlink(path)
-
使用场景:通常 Linux/Unix 系统里更习惯使用
unlink
,表示从文件系统的目录树中“解除链接”。
-
-
使用
pathlib.Path.unlink()
-
面向对象方式:
from pathlib import Pathp = Path("example.txt") if p.exists():p.unlink() # 删除文件print("删除成功!")
-
删除目录与文件的区别
- 删除文件 →
os.remove()
/os.unlink()
/Path.unlink()
- 删除空目录 →
os.rmdir()
/Path.rmdir()
- 删除非空目录 →
shutil.rmtree(path)
(递归删除整个文件夹)
常见错误与解决方法
-
FileNotFoundError
- 当指定的文件不存在时会报错。
- 解决方法:在删除前加
os.path.exists(path)
或Path.exists()
检查。
if os.path.exists("old.txt"):os.remove("old.txt") else:print("文件不存在!")
-
PermissionError
- 文件正在被占用(Windows 常见,比如文件在记事本中打开)。
- 解决方法:关闭占用文件的程序,或在 Linux 上使用
sudo
。
-
IsADirectoryError
- 如果用
os.remove()
删除目录,会报错。 - 解决方法:用
os.rmdir()
或shutil.rmtree()
删除目录。
- 如果用
移动和复制文件
文件移动(Moving Files)
在 Python 中,文件移动操作常用 shutil.move()
。
shutil.move(src, dst)
-
作用:把文件或目录从
src
移动到dst
。 -
语法:
import shutilshutil.move(src, dst)
-
参数说明:
src
:源文件或目录路径dst
:目标路径(可以是目录,也可以是新文件名)
-
特点:
- 如果
dst
是目录 → 文件被移动到该目录下,保留原名称。 - 如果
dst
是文件名 → 文件会被移动并重命名。 - 如果
dst
存在同名文件 → 会覆盖。
- 如果
移动文件示例
import shutil# 将 data.txt 移动到 backup 目录下
shutil.move("data.txt", "backup/")# 将 log.txt 移动并重命名为 log_old.txt
shutil.move("log.txt", "backup/log_old.txt")
移动目录
import shutil# 移动整个文件夹
shutil.move("my_folder", "archive/")
与 os.rename()
的区别
os.rename()
:只能在同一磁盘/分区下移动,跨分区会报错。shutil.move()
:支持跨分区,会自动处理。
一般推荐用shutil.move()
,更通用。
与 os.rename()
的区别
os.rename()
:只能在同一磁盘/分区下移动,跨分区会报错。shutil.move()
:支持跨分区,会自动处理。
一般推荐用shutil.move()
,更通用。
文件复制(Copying Files)
复制文件主要用 shutil
模块 的几个方法,不同方法保留的信息不同。
-
shutil.copy(src, dst)
-
作用:复制文件内容到新路径。
-
特点:
- 只复制文件内容和权限(mode),不复制元数据(如创建时间、修改时间等)。
- 如果
dst
是目录 → 保持原文件名。
-
示例:
import shutil# 将 data.txt 复制到 backup/ 目录下 shutil.copy("data.txt", "backup/")# 将 log.txt 复制并重命名 shutil.copy("log.txt", "backup/log_copy.txt")
-
-
shutil.copy(src, dst)
-
作用:复制文件内容到新路径。
-
特点:
- 只复制文件内容和权限(mode),不复制元数据(如创建时间、修改时间等)。
- 如果
dst
是目录 → 保持原文件名。
-
示例:
import shutil# 将 data.txt 复制到 backup/ 目录下 shutil.copy("data.txt", "backup/")# 将 log.txt 复制并重命名 shutil.copy("log.txt", "backup/log_copy.txt")
-
-
shutil.copy2(src, dst)
-
作用:在
copy
的基础上,尽可能保留文件的元数据(修改时间、访问时间等)。 -
推荐:如果要保留完整文件信息,最好用
copy2
。 -
示例:
import shutilshutil.copy2("data.txt", "backup/")
-
方法对比
方法 | 是否保留权限 | 是否保留元数据(时间戳等) | dst 可以是目录? |
---|---|---|---|
shutil.copyfile | 否 | 否 | 否 |
shutil.copy | 是 | 否 | 是 |
shutil.copy2 | 是 | 是 | 是 |
一般用 copy2
,除非你只想要文件内容。
目录复制
如果需要复制整个目录,使用 shutil.copytree()
。
shutil.copytree(src, dst, dirs_exist_ok=False)
-
作用:递归复制整个目录树(目录及其内容)。
-
语法:
shutil.copytree(src, dst, dirs_exist_ok=False)
-
参数说明:
src
:源目录dst
:目标目录(必须不存在,除非dirs_exist_ok=True
)dirs_exist_ok=True
:允许复制到已存在的目录(Python 3.8+)。
-
示例:
import shutil# 将 my_folder 整个复制到 backup/my_folder shutil.copytree("my_folder", "backup/my_folder")# 覆盖到已存在目录(Python 3.8+) shutil.copytree("my_folder", "backup/my_folder", dirs_exist_ok=True)
总结表
操作 | 方法 | 说明 |
---|---|---|
移动文件/目录 | shutil.move(src, dst) | 支持跨分区,覆盖同名文件 |
复制文件(仅内容) | shutil.copyfile(src, dst) | 不保留权限/元数据 |
复制文件(含权限) | shutil.copy(src, dst) | 保留权限,不保留时间戳 |
复制文件(完整) | shutil.copy2(src, dst) | 保留权限 + 元数据 |
复制目录 | shutil.copytree(src, dst) | 递归复制整个目录 |
目录操作(文件夹管理)
获取和切换工作目录
-
获取当前工作目录
-
os.getcwd()
import osprint(os.getcwd()) # 输出当前脚本运行所在的目录
-
Path.cwd()
(推荐写法)from pathlib import Pathprint(Path.cwd()) # Path 对象,支持链式操作
-
-
切换工作目录
-
os.chdir(path)
import osos.chdir("/Users/username/Documents") print(os.getcwd()) # 切换后的路径
注意:切换目录后,后续所有相对路径操作都会基于新的工作目录。
-
创建目录
-
创建单级目录
-
os.mkdir(path)
import osos.mkdir("new_folder") # 在当前目录下新建文件夹
-
Path.mkdir()
from pathlib import PathPath("new_folder").mkdir()
-
-
创建多级目录
-
os.makedirs(path)
import osos.makedirs("a/b/c") # 递归创建 a/b/c 目录
-
Path.mkdir(parents=True)
from pathlib import PathPath("a/b/c").mkdir(parents=True, exist_ok=True)
-
参数说明:
parents=True
:允许递归创建父目录exist_ok=True
:目录存在时不报错(默认会报 FileExistsError)
-
删除目录
-
删除空目录
-
os.rmdir(path)
import osos.rmdir("new_folder") # 只能删除空目录
-
Path.rmdir()
from pathlib import PathPath("new_folder").rmdir()
-
-
删除非空目录
-
shutil.rmtree(path)
import shutilshutil.rmtree("a") # 删除整个目录树(a 及其子目录和文件)
危险操作:
rmtree
会递归删除所有内容,请谨慎使用。 -
遍历目录
-
os.listdir(path)
import osfiles = os.listdir(".") # 当前目录 print(files)
输出是一个列表,包含文件和目录名称。
-
os.scandir(path)
(效率更高)import oswith os.scandir(".") as entries:for entry in entries:print(entry.name, "是目录吗?", entry.is_dir())
返回
DirEntry
对象,比listdir()
更快,可以直接判断文件类型。 -
Path.iterdir()
(推荐)from pathlib import Pathfor item in Path(".").iterdir():if item.is_file():print("文件:", item.name)elif item.is_dir():print("目录:", item.name)
路径拼接与处理
-
os.path
方式import ospath = os.path.join("folder", "file.txt") # 拼接路径 print(path)print(os.path.basename(path)) # file.txt print(os.path.dirname(path)) # folder print(os.path.splitext(path)) # ('folder/file', '.txt') print(os.path.abspath("file.txt")) # 转为绝对路径
-
pathlib
方式(更现代)from pathlib import Pathp = Path("folder") / "file.txt" # 用 / 拼接路径 print(p)print(p.name) # file.txt print(p.parent) # folder print(p.suffix) # .txt print(p.stem) # file print(p.resolve()) # 绝对路径
目录操作常见应用场景
-
批量处理文件
from pathlib import Pathfor file in Path("logs").glob("*.txt"):print("处理文件:", file)
-
按日期创建日志目录
from pathlib import Path import datetimetoday = datetime.date.today().isoformat() Path(f"logs/{today}").mkdir(parents=True, exist_ok=True)
-
递归遍历所有文件
from pathlib import Pathfor file in Path("project").rglob("*.py"):print(file)