Python 文件操作
1. 文件操作概述
文件操作是编程中非常常见且重要的功能,它允许程序与外部文件进行交互,读取存储在文件中的数据,或将程序生成的数据写入文件。Python 提供了简单而强大的内置函数和模块来处理文件。
2. 文件打开与关闭
在进行文件操作之前,首先需要打开文件。完成操作后,务必关闭文件以释放系统资源。
2.1 open() 函数
使用内置的 open() 函数来打开文件。它至少需要一个文件路径参数,通常还会指定打开模式。
# 基本用法:打开一个文件对象
file_object = open("path/to/your/file.txt", "mode", encoding="utf-8")
file_path(文件路径) : 字符串类型,表示要打开的文件的路径(可以是相对路径或绝对路径)。mode(打开模式) : 字符串类型,指定文件打开的方式(读、写、追加、二进制等)。encoding(编码) : 可选参数,指定文件的编码格式,对于文本文件非常重要,例如'utf-8'。如果不指定,会使用系统默认编码。
2.2 文件打开模式
文件打开模式决定了你可以对文件执行的操作(读、写、追加)以及文件的处理方式(文本或二进制)。
| 模式 | 描述 | 示例 |
|---|---|---|
'r' | 读取模式 (read) :默认模式。文件必须存在,否则会抛出 FileNotFoundError。文件指针位于文件开头。 | open('file.txt', 'r') |
'w' | 写入模式 (write) :如果文件不存在,则创建新文件。如果文件已存在,则截断 (清空) 文件内容。文件指针位于文件开头。 | open('file.txt', 'w') |
'a' | 追加模式 (append) :如果文件不存在,则创建新文件。如果文件已存在,则在文件末尾追加内容,不会清空原有内容。文件指针位于文件末尾。 | open('file.txt', 'a') |
'x' | 独占创建模式 (exclusive creation) :如果文件已存在,则抛出 FileExistsError。如果文件不存在,则创建并以写入模式打开。用于确保文件是新创建的。 | open('file.txt', 'x') |
'b' | 二进制模式 (binary) :用于处理非文本文件(如图片、视频、音频等)。必须与 'r', 'w', 'a', 'x' 中的一个结合使用,例如 'rb', 'wb'。在此模式下,读写的数据是字节 (bytes)。 | open('image.png', 'rb') |
't' | 文本模式 (text) :默认模式。用于处理文本文件。读写的数据是字符串。可以与 'r', 'w', 'a', 'x' 中的一个结合使用,例如 'rt', 'wt'。 | open('text.txt', 'rt') (等同于 'r') |
'+' | 更新模式 (update) :表示可以同时进行读写操作。必须与 'r', 'w', 'a', 'x' 中的一个结合使用,例如 'r+' (读写,文件必须存在), 'w+' (写读,清空文件或创建), 'a+' (追加读写)。 | open('data.txt', 'r+') |
常见模式组合:
'r':只读,文件不存在会报错。'w':只写,文件不存在则创建,存在则清空内容。'a':只写,文件不存在则创建,存在则在末尾追加内容。'rb':只读二进制文件。'wb':只写二进制文件。'r+':读写模式,文件必须存在。'w+':写读模式,文件不存在则创建,存在则清空内容。'a+':追加读写模式,文件不存在则创建,存在则在末尾追加内容。
2.3 关闭文件
文件操作完成后,必须关闭文件以释放系统资源,并确保所有写入操作都已同步到磁盘。
推荐方式:使用 with 语句 (上下文管理器)
with 语句是处理文件操作的最佳实践。它能确保文件在操作完成后被正确关闭,即使在文件操作过程中发生异常,也能自动处理关闭。
# 写入文本文件示例
try:
with open("example_write.txt", "w", encoding="utf-8") as file:
file.write("Hello, Python!\n")
file.write("这是使用 with 语句写入的第二行。\n")
print("内容已成功写入 example_write.txt")
except IOError as e:
print(f"写入文件时发生错误: {e}")
# 读取文本文件示例
try:
with open("example_write.txt", "r", encoding="utf-8") as file:
content = file.read()
print("\n读取到的内容:")
print(content)
except FileNotFoundError:
print("错误: example_write.txt 文件不存在。")
except IOError as e:
print(f"读取文件时发生错误: {e}")
# 追加内容到文本文件示例
try:
with open("example_write.txt", "a", encoding="utf-8") as file:
file.write("这是追加的新内容。\n")
print("\n新内容已追加到 example_write.txt")
except IOError as e:
print(f"追加内容时发生错误: {e}")
# 处理二进制文件示例 (写入一些字节)
try:
with open("binary_data.bin", "wb") as file:
file.write(b'\x01\x02\x03\x04\xff\x00') # 写入字节数据
print("\n二进制内容已写入 binary_data.bin")
except IOError as e:
print(f"写入二进制文件时发生错误: {e}")
# 读取二进制文件示例
try:
with open("binary_data.bin", "rb") as file:
binary_content = file.read()
print("读取到的二进制内容:", binary_content)
except FileNotFoundError:
print("错误: binary_data.bin 文件不存在。")
except IOError as e:
print(f"读取二进制文件时发生错误: {e}")
传统方式:open() 和 close()
这种方式需要你手动调用 file_object.close() 方法来关闭文件。如果忘记关闭或在操作过程中发生异常,文件可能不会被正确关闭,导致数据丢失或资源泄露。
file_obj = None # 初始化文件对象为 None
try:
file_obj = open("manual_close_example.txt", "w", encoding="utf-8")
file_obj.write("这是一个手动关闭文件的例子。\n")
# 模拟一个可能导致异常的操作
# 1 / 0
except IOError as e:
print(f"文件操作错误: {e}")
except Exception as e:
print(f"发生其他错误: {e}")
finally:
# 无论是否发生异常,都会执行 finally 块,确保文件被关闭
if file_obj: # 检查文件对象是否已成功创建
file_obj.close()
print("文件已手动关闭。")
3. 读取文件内容
一旦文件以读取模式打开,就可以使用多种方法来获取其内容。
3.1 file.read(size=-1)
- 功能: 读取文件中的所有内容并作为字符串返回 (文本模式) 或字节串返回 (二进制模式)。
size参数: 可选。如果提供了size,则读取指定数量的字符 (文本模式) 或字节 (二进制模式)。如果省略或为负数,则读取整个文件。- 文件指针: 读取后,文件指针会移动到读取内容的末尾。
with open("example_read.txt", "w", encoding="utf-8") as f:
f.write("第一行文本。\n")
f.write("第二行文本。\n")
f.write("第三行文本。\n")
print("--- 使用 read() 读取 ---")
with open("example_read.txt", "r", encoding="utf-8") as f:
# 读取前5个字符
first_chars = f.read(5)
print(f"读取前5个字符: '{first_chars}'") # 文件指针移动到第5个字符后
# 继续读取剩余所有内容
rest_content = f.read()
print(f"读取剩余内容:\n'{rest_content}'") # 文件指针移动到文件末尾
# 再次调用 read() 将返回空字符串,因为已到文件末尾
empty_read = f.read()
print(f"再次读取 (空): '{empty_read}'")
3.2 file.readline(size=-1)
- 功能: 读取文件中的一行内容,包括行末的换行符
\n。 size参数: 可选。如果提供了size,则最多读取指定数量的字符。- 文件指针: 读取一行后,文件指针会移动到下一行的开头。
print("\n--- 使用 readline() 读取 ---")
with open("example_read.txt", "r", encoding="utf-8") as f:
line1 = f.readline()
print(f"读取第一行: '{line1.strip()}'") # .strip() 用于移除首尾空白符,包括换行符
line2 = f.readline()
print(f"读取第二行: '{line2.strip()}'")
line3 = f.readline()
print(f"读取第三行: '{line3.strip()}'")
empty_line = f.readline()
print(f"再次读取 (空): '{empty_line}'")
3.3 file.readlines()
- 功能: 读取文件中的所有行,并将它们作为字符串列表返回。列表中的每个字符串都包含行末的换行符
\n。 - 文件指针: 读取后,文件指针会移动到文件末尾。
print("\n--- 使用 readlines() 读取 ---")
with open("example_read.txt", "r", encoding="utf-8") as f:
all_lines = f.readlines()
print("所有行列表:")
for line in all_lines:
print(f"'{line.strip()}'")
3.4 直接迭代文件对象 (推荐用于大文件)
- 功能: 文件对象本身是可迭代的。可以直接在
for循环中遍历文件对象,每次迭代读取一行。 - 优点: 这种方式内存效率非常高,因为它不会一次性将整个文件加载到内存中,而是逐行读取,特别适合处理大型文件。
- 文件指针: 逐行读取,指针随之移动。
print("\n--- 通过迭代文件对象逐行读取 (推荐) ---")
with open("example_read.txt", "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
print(f"第 {line_num} 行: '{line.strip()}'")
4. 写入文件内容
一旦文件以写入 ('w') 或追加 ('a') 模式打开,就可以向其中写入内容。
4.1 file.write(string)
- 功能: 将指定的字符串写入文件。
- 返回值: 返回写入的字符数。
- 注意:
write()方法不会自动添加换行符。如果你希望每段内容单独成行,需要手动在字符串末尾添加\n。
print("\n--- 使用 write() 写入 ---")
# 'w' 模式会清空文件内容再写入
with open("example_write_single.txt", "w", encoding="utf-8") as f:
f.write("这是第一行,后面有换行符。\n")
f.write("这是第二行,后面也有换行符。\n")
f.write("这是第三行,后面没有换行符。") # 注意这里没有 \n
print("内容已写入 example_write_single.txt")
# 验证写入结果
with open("example_write_single.txt", "r", encoding="utf-8") as f:
print("\nexample_write_single.txt 的内容:")
print(f.read())
4.2 file.writelines(list_of_strings)
- 功能: 将字符串列表中的所有字符串写入文件。
- 注意:
writelines()也不会自动添加换行符。你需要确保列表中的每个字符串都包含\n,以便它们在文件中单独成行。
print("\n--- 使用 writelines() 写入 ---")
lines_to_write = [
"列表写入的第一行。\n",
"列表写入的第二行。\n",
"列表写入的第三行 (这个也有换行符)。\n",
"列表写入的第四行 (这个没有换行符)。"
]
with open("example_writelines.txt", "w", encoding="utf-8") as f:
f.writelines(lines_to_write)
print("内容已通过 writelines 写入 example_writelines.txt")
# 验证写入结果
with open("example_writelines.txt", "r", encoding="utf-8") as f:
print("\nexample_writelines.txt 的内容:")
print(f.read())
5. 文件指针操作
文件指针(或文件游标)指示了文件当前读写的位置。
5.1 file.tell()
- 功能: 返回文件当前指针的位置。对于文本文件,这个位置是字符数;对于二进制文件,是字节数。
- 返回值: 一个整数,表示从文件开头算起的偏移量。
print("\n--- 文件指针操作: tell() ---")
with open("example_pointer.txt", "w+", encoding="utf-8") as f: # w+ 模式允许读写
f.write("Hello World!") # 写入 12 个字符
print(f"写入后指针位置: {f.tell()}") # 12
f.seek(0) # 将指针移回文件开头
print(f"移回开头后指针位置: {f.tell()}") # 0
content = f.read(5) # 读取前5个字符
print(f"读取5个字符后指针位置: {f.tell()}") # 5
print(f"读取内容: '{content}'")
5.2 file.seek(offset, whence=0)
-
功能: 改变文件指针的位置。
-
offset: 偏移量,表示从whence指定的位置开始移动的字节数。 -
whence: 偏移的参考位置。0(默认值): 文件开头。offset必须是非负整数。1: 当前位置。offset可以是正数(向前移动)或负数(向后移动)。仅在二进制模式下可用。2: 文件末尾。offset通常是负数(从末尾向前移动)。仅在二进制模式下可用。
-
重要注意事项:
- 在文本模式下,
seek()的offset参数只能是0(文件开头),或者由tell()返回的值。这是因为文本编码(如 UTF-8)中一个字符可能占用多个字节,导致精确的字节偏移量难以计算。 - 对于任意偏移量,通常需要在二进制模式 (
'b') 下进行seek()操作。
- 在文本模式下,
print("\n--- 文件指针操作: seek() ---")
# 文本模式下的 seek
with open("example_seek_text.txt", "w+", encoding="utf-8") as f:
f.write("Python 文件操作示例。\n")
f.write("这是第二行。\n")
f.seek(0) # 移回文件开头
print(f"文本模式下,移回开头后读取: '{f.read()}'")
f.seek(6) # 文本模式下,除了0和tell()的返回值,其他seek可能会有意外行为或报错
# print(f.read()) # 可能会报错或读取不完整字符
# 二进制模式下的 seek (更灵活)
with open("example_seek_binary.bin", "wb+") as f: # wb+ 允许读写二进制
f.write(b"ABCDEFGH") # 写入 8 字节
print(f"二进制模式,写入后指针: {f.tell()}") # 8
f.seek(2) # 从开头偏移 2 字节
print(f"二进制模式,seek(2) 后指针: {f.tell()}") # 2
print(f"读取: {f.read(3)}") # 读取 3 字节,输出 b'CDE'
f.seek(-2, 2) # 从文件末尾向前偏移 2 字节 (whence=2)
print(f"二进制模式,seek(-2, 2) 后指针: {f.tell()}") # 6 (8-2)
print(f"读取: {f.read(2)}") # 读取 2 字节,输出 b'GH'
f.seek(1, 1) # 从当前位置向前偏移 1 字节 (whence=1)
print(f"二进制模式,seek(1, 1) 后指针: {f.tell()}") # 9 (8+1) - 实际是7+1=8, 因为上面读了2个字节,指针在8
# 实际上,这里f.tell()会是8,因为上次read(2)后指针在8,再seek(1,1)就会到9。
# 为了演示,我们重置一下指针
f.seek(0)
f.read(5) # 指针到5
print(f"指针在5,seek(1,1) 后指针: {f.tell()}") # 5
f.seek(1, 1) # 从当前位置向前偏移 1 字节
print(f"指针在5,seek(1,1) 后指针: {f.tell()}") # 6
print(f"读取: {f.read(2)}") # 输出 b'FG'
6. 文件路径操作 (os 模块和 shutil 模块)
os 模块提供了与操作系统交互的功能,包括文件和目录的创建、删除、重命名、判断等。shutil 模块则提供了一些更高级的文件操作,如复制、移动、删除非空目录等。
import os
import shutil
# --- 路径获取与拼接 ---
print("\n--- 文件路径操作 ---")
# 获取当前工作目录
current_directory = os.getcwd()
print(f"当前工作目录: {current_directory}")
# 路径拼接 (推荐使用 os.path.join(),因为它能自动处理不同操作系统的路径分隔符)
folder_name = "my_data_folder"
file_name = "report.txt"
full_path = os.path.join(current_directory, folder_name, file_name)
print(f"拼接后的文件路径: {full_path}")
# --- 目录操作 ---
# 创建单级目录
new_dir = "temp_dir"
if not os.path.exists(new_dir):
os.mkdir(new_dir)
print(f"已创建目录: {new_dir}")
else:
print(f"目录 {new_dir} 已存在。")
# 创建多级目录 (如果父目录不存在也会一并创建)
nested_dir = "project/src/data"
if not os.path.exists(nested_dir):
os.makedirs(nested_dir)
print(f"已创建多级目录: {nested_dir}")
else:
print(f"目录 {nested_dir} 已存在。")
# 列出目录内容
print(f"\n'{current_directory}' 目录内容:")
for item in os.listdir(current_directory):
print(f"- {item}")
# --- 文件/目录判断 ---
# 创建一个测试文件
with open("test_file.txt", "w") as f:
f.write("这是一个测试文件。")
print("\n--- 文件/目录判断 ---")
print(f"'test_file.txt' 是否存在: {os.path.exists('test_file.txt')}")
print(f"'test_file.txt' 是否是文件: {os.path.isfile('test_file.txt')}")
print(f"'{new_dir}' 是否是目录: {os.path.isdir(new_dir)}")
print(f"'non_existent_item' 是否存在: {os.path.exists('non_existent_item')}")
# --- 文件/目录重命名 ---
# os.rename("old_name.txt", "new_name.txt") # 重命名文件
# os.rename("old_dir", "new_dir") # 重命名目录
# --- 文件/目录删除 ---
print("\n--- 文件/目录删除 ---")
# 删除文件
if os.path.exists("test_file.txt"):
os.remove("test_file.txt")
print("test_file.txt 已删除。")
# 删除空目录
if os.path.exists(new_dir):
os.rmdir(new_dir)
print(f"空目录 '{new_dir}' 已删除。")
# 删除非空目录 (使用 shutil.rmtree)
# 注意:这个操作会递归删除目录及其所有内容,请谨慎使用!
if os.path.exists("project"):
shutil.rmtree("project")
print("非空目录 'project' 及其所有内容已删除。")
7. 错误处理
在进行文件操作时,可能会遇到各种错误,例如文件不存在、权限不足、磁盘空间不足等。使用 try-except 块是处理这些错误的最佳实践,以增强程序的健壮性。
print("\n--- 错误处理 ---")
# 尝试打开一个不存在的文件
try:
with open("non_existent_file.txt", "r") as f:
content = f.read()
print(content)
except FileNotFoundError:
print("错误: 文件 'non_existent_file.txt' 不存在!")
except Exception as e: # 捕获其他可能的异常
print(f"发生未知错误: {e}")
# 尝试写入一个可能没有权限的路径 (例如系统根目录)
# 注意:在某些操作系统或环境下,这可能会真的尝试写入并引发权限错误
try:
# 假设我们尝试写入一个受保护的路径 (例如 Linux/macOS 的 /root 或 Windows 的 C:\Windows)
# 这里只是一个示例,实际运行时请勿尝试写入敏感系统目录
# with open("/root/protected_file.txt", "w") as f:
# f.write("尝试写入受保护的文件。")
print("跳过写入受保护路径的示例,以避免权限错误。")
except PermissionError:
print("错误: 没有足够的权限写入该文件或目录!")
except IOError as e:
print(f"发生IO错误: {e}")
except Exception as e:
print(f"发生未知错误: {e}")
# 确保文件关闭的 finally 块示例 (虽然 with 语句更推荐)
file_handle = None
try:
file_handle = open("temp_file_for_error.txt", "w")
file_handle.write("写入一些数据...")
# 模拟一个错误
# 1 / 0
except ZeroDivisionError:
print("捕获到 ZeroDivisionError。")
except Exception as e:
print(f"捕获到其他异常: {e}")
finally:
if file_handle:
file_handle.close()
print("在 finally 块中关闭了文件。")
8. 常见文件操作场景
8.1 逐行读取大文件
这是处理日志文件或大型数据集的常用方法,可以避免一次性将整个文件加载到内存中。
print("\n--- 常见场景: 逐行读取大文件 ---")
# 创建一个模拟的大文件
with open("large_log.txt", "w", encoding="utf-8") as f:
for i in range(1000):
f.write(f"日志条目 {i}: 这是一个模拟的日志信息。\n")
line_count = 0
with open("large_log.txt", "r", encoding="utf-8") as f:
for line in f:
line_count += 1
if line_count <= 5: # 只打印前5行作为示例
print(f"读取行 {line_count}: {line.strip()}")
elif line_count == 6:
print("...(更多行)...")
print(f"总共读取了 {line_count} 行。")
os.remove("large_log.txt") # 清理测试文件
8.2 CSV 文件读写
使用 Python 内置的 csv 模块可以方便地处理 CSV (Comma Separated Values) 文件。
import csv
print("\n--- 常见场景: CSV 文件读写 ---")
# 写入 CSV 文件
csv_data = [
["Name", "Age", "City"],
["Alice", 30, "New York"],
["Bob", 24, "London"],
["Charlie", 35, "Paris"]
]
with open("people.csv", "w", newline="", encoding="utf-8") as csvfile:
csv_writer = csv.writer(csvfile)
csv_writer.writerows(csv_data)
print("数据已写入 people.csv")
# 读取 CSV 文件
with open("people.csv", "r", newline="", encoding="utf-8") as csvfile:
csv_reader = csv.reader(csvfile)
print("\n读取到的 CSV 内容:")
for row in csv_reader:
print(row)
# 使用 DictReader/DictWriter (将行作为字典处理)
print("\n--- 常见场景: CSV 文件读写 (字典方式) ---")
dict_data = [
{"Name": "David", "Age": 28, "City": "Berlin"},
{"Name": "Eve", "Age": 29, "City": "Tokyo"}
]
with open("more_people.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["Name", "Age", "City"]
csv_writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
csv_writer.writeheader() # 写入表头
csv_writer.writerows(dict_data)
print("字典数据已写入 more_people.csv")
with open("more_people.csv", "r", newline="", encoding="utf-8") as csvfile:
csv_reader = csv.DictReader(csvfile)
print("\n读取到的 CSV 内容 (字典方式):")
for row in csv_reader:
print(row)
os.remove("people.csv") # 清理测试文件
os.remove("more_people.csv") # 清理测试文件
8.3 JSON 文件读写
使用 Python 内置的 json 模块可以方便地处理 JSON (JavaScript Object Notation) 文件。
import json
print("\n--- 常见场景: JSON 文件读写 ---")
# 写入 JSON 文件
data_to_write = {
"name": "Python User",
"age": 42,
"is_active": True,
"courses": ["Python Basics", "File Operations", "Web Scraping"],
"address": {
"street": "123 Main St",
"city": "Anytown"
}
}
with open("data.json", "w", encoding="utf-8") as json_file:
json.dump(data_to_write, json_file, indent=4, ensure_ascii=False)
# indent=4 使输出更易读,ensure_ascii=False 确保非ASCII字符正常显示
print("数据已写入 data.json")
# 读取 JSON 文件
with open("data.json", "r", encoding="utf-8") as json_file:
loaded_data = json.load(json_file)
print("\n读取到的 JSON 内容:")
print(loaded_data)
print(f"用户名: {loaded_data['name']}")
print(f"课程列表: {loaded_data['courses']}")
os.remove("data.json") # 清理测试文件