第6章 文件操作¶
文件操作是Python编程中最基础也是最重要的技能之一。无论是读取配置文件、处理数据文件,还是保存程序运行结果,我们都需要与文件系统进行交互。本章将详细介绍Python中文件操作的各种方法和最佳实践。
本系列文章所使用到的示例源码:Python从入门到精通示例代码
6.1 文件的打开与关闭¶
文件操作的基本概念¶
文件系统简介¶
文件系统是操作系统用来管理文件和目录的方式。在Python中,我们可以通过内置函数和模块来操作文件系统中的文件。文件操作的基本流程包括:打开文件 → 读取/写入数据 → 关闭文件。
文件路径¶
文件路径分为两种类型:
- 绝对路径:从根目录开始的完整路径,如 C:\Users\username\documents\file.txt(Windows)或 /home/username/documents/file.txt(Linux/Mac)
- 相对路径:相对于当前工作目录的路径,如 ./data/file.txt 或 ../config/settings.txt
文件类型¶
- 文本文件:包含可读字符的文件,如
.txt、.py、.csv等 - 二进制文件:包含二进制数据的文件,如图片、音频、视频、可执行文件等
open()函数详解¶
open() 函数是Python中打开文件的标准方法,其基本语法如下:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
基本用法示例¶
# 创建一个测试文件
with open('test_files/sample.txt', 'w', encoding='utf-8') as f:
f.write('Hello, World!\nThis is line 2.\nThis is line 3.\n这是中文内容。\n文件操作示例。')
# 打开文件进行读取
file = open('test_files/sample.txt', 'r', encoding='utf-8')
content = file.read()
print(content)
file.close()
输出结果:
Hello, World!
This is line 2.
This is line 3.
这是中文内容。
文件操作示例。
打开模式参数详解¶
| 模式 | 描述 | 文件指针位置 | 文件不存在时 |
|---|---|---|---|
| ‘r’ | 只读模式(默认) | 文件开头 | 抛出异常 |
| ‘w’ | 写入模式 | 文件开头 | 创建新文件 |
| ‘a’ | 追加模式 | 文件末尾 | 创建新文件 |
| ‘x’ | 独占创建模式 | 文件开头 | 文件存在时抛出异常 |
| ‘b’ | 二进制模式 | - | 与其他模式组合使用 |
| ‘t’ | 文本模式(默认) | - | 与其他模式组合使用 |
| ’+’ | 读写模式 | - | 与其他模式组合使用 |
# 不同模式的示例
# 写入模式 - 会覆盖原文件内容
with open('test_files/write_test.txt', 'w', encoding='utf-8') as f:
f.write('这是写入模式的内容')
# 追加模式 - 在文件末尾添加内容
with open('test_files/write_test.txt', 'a', encoding='utf-8') as f:
f.write('\n这是追加的内容')
# 读取并显示结果
with open('test_files/write_test.txt', 'r', encoding='utf-8') as f:
print(f.read())
编码参数¶
在处理文本文件时,指定正确的编码格式非常重要:
# 使用UTF-8编码(推荐)
with open('test_files/chinese.txt', 'w', encoding='utf-8') as f:
f.write('你好,世界!\nHello, World!')
# 读取时也要指定相同的编码
with open('test_files/chinese.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
文件对象的属性¶
文件对象具有多个有用的属性:
file = open('test_files/sample.txt', 'r', encoding='utf-8')
print(f"文件名: {file.name}")
print(f"打开模式: {file.mode}")
print(f"编码格式: {file.encoding}")
print(f"是否已关闭: {file.closed}")
file.close()
print(f"关闭后是否已关闭: {file.closed}")
输出结果:
文件名: test_files/sample.txt
文件模式: r
编码: utf-8
是否关闭: False
with语句外,文件是否关闭: True
close()方法的重要性¶
手动关闭文件是一个好习惯,它可以:
- 释放系统资源
- 确保数据被写入磁盘
- 避免文件锁定问题
# 不推荐的方式 - 容易忘记关闭文件
file = open('test_files/sample.txt', 'r')
content = file.read()
# 如果这里发生异常,文件可能不会被关闭
file.close()
# 推荐的方式 - 使用try-finally确保文件被关闭
try:
file = open('test_files/sample.txt', 'r')
content = file.read()
finally:
file.close()
6.2 文件的读取方法¶
文本文件读取¶
Python提供了多种读取文件的方法,每种方法都有其适用场景。
read()方法¶
read() 方法可以读取整个文件或指定数量的字符:
# 读取整个文件
with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
content = f.read()
print("文件内容:")
print(repr(content))
输出结果:
文件内容: 'Hello, World!\nThis is line 2.\nThis is line 3.\n这是中文内容。\n文件操作示例。'
readline()方法¶
readline() 方法每次读取一行:
with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
print("逐行读取:")
for i, line in enumerate(f, 1):
print(f"第{i}行: {repr(line)}")
输出结果:
逐行读取:
第1行: 'Hello, World!\n'
第2行: 'This is line 2.\n'
第3行: 'This is line 3.\n'
第4行: '这是中文内容。\n'
第5行: '文件操作示例。'
readlines()方法¶
readlines() 方法读取所有行并返回列表:
with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
print("所有行:")
print(lines)
输出结果:
所有行: ['Hello, World!\n', 'This is line 2.\n', 'This is line 3.\n', '这是中文内容。\n', '文件操作示例。']
逐行读取的最佳实践¶
对于大文件,直接遍历文件对象是最内存高效的方法:
# 创建一个较大的测试文件
with open('test_files/large_file.txt', 'w', encoding='utf-8') as f:
for i in range(1000):
f.write(f"这是第{i+1}行内容\n")
# 内存高效的逐行读取
print("内存高效的逐行读取(只显示前5行和后5行):")
with open('test_files/large_file.txt', 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
if line_num <= 5 or line_num > 995:
print(f"第{line_num}行: {line.strip()}")
elif line_num == 6:
print("... (省略中间行) ...")
二进制文件读取¶
对于二进制文件,需要使用 ‘rb’ 模式:
# 创建一个简单的二进制文件
binary_data = bytes([0x48, 0x65, 0x6C, 0x6C, 0x6F]) # "Hello" 的ASCII码
with open('test_files/binary_file.bin', 'wb') as f:
f.write(binary_data)
# 读取二进制文件
with open('test_files/binary_file.bin', 'rb') as f:
data = f.read()
print(f"二进制数据: {data}")
print(f"转换为字符串: {data.decode('ascii')}")
print(f"十六进制表示: {data.hex()}")
大文件处理策略¶
对于非常大的文件,分块读取是更好的选择:
def read_large_file_in_chunks(filename, chunk_size=1024):
"""
分块读取大文件
Args:
filename: 文件名
chunk_size: 每次读取的字节数
"""
with open(filename, 'r', encoding='utf-8') as f:
chunk_num = 1
while True:
chunk = f.read(chunk_size)
if not chunk:
break
print(f"块 {chunk_num}: {len(chunk)} 个字符")
# 这里可以处理chunk数据
chunk_num += 1
if chunk_num > 3: # 只显示前3块作为示例
print("...")
break
# 使用分块读取
print("\n分块读取大文件:")
read_large_file_in_chunks('test_files/large_file.txt', chunk_size=100)
6.3 文件的写入操作¶
文本文件写入¶
write()方法¶
write() 方法用于写入字符串,返回写入的字符数:
# 基本写入操作
with open('test_files/write_demo.txt', 'w', encoding='utf-8') as f:
chars_written = f.write('Hello, World!')
print(f"写入了 {chars_written} 个字符")
# 写入多行
f.write('\n第二行内容')
f.write('\n第三行内容')
# 验证写入结果
with open('test_files/write_demo.txt', 'r', encoding='utf-8') as f:
print("写入的内容:")
print(f.read())
writelines()方法¶
writelines() 方法用于写入字符串列表:
lines = ['第一行\n', '第二行\n', '第三行\n']
with open('test_files/writelines_demo.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
# 注意:writelines不会自动添加换行符
lines_without_newline = ['行1', '行2', '行3']
with open('test_files/no_newline.txt', 'w', encoding='utf-8') as f:
f.writelines(lines_without_newline)
print("writelines结果(有换行符):")
with open('test_files/writelines_demo.txt', 'r', encoding='utf-8') as f:
print(repr(f.read()))
print("\nwritelines结果(无换行符):")
with open('test_files/no_newline.txt', 'r', encoding='utf-8') as f:
print(repr(f.read()))
写入模式的区别¶
# 创建初始文件
with open('test_files/mode_test.txt', 'w', encoding='utf-8') as f:
f.write('原始内容')
print("原始内容:")
with open('test_files/mode_test.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 'w'模式:覆盖写入
with open('test_files/mode_test.txt', 'w', encoding='utf-8') as f:
f.write('覆盖后的内容')
print("\n'w'模式后的内容:")
with open('test_files/mode_test.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 'a'模式:追加写入
with open('test_files/mode_test.txt', 'a', encoding='utf-8') as f:
f.write('\n追加的内容')
print("\n'a'模式后的内容:")
with open('test_files/mode_test.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 'x'模式:独占创建(文件不存在时创建,存在时报错)
try:
with open('test_files/exclusive_file.txt', 'x', encoding='utf-8') as f:
f.write('独占创建的文件')
print("\n成功创建独占文件")
except FileExistsError:
print("\n文件已存在,无法使用'x'模式")
二进制文件写入¶
# 写入二进制数据
binary_data = b'\x89PNG\r\n\x1a\n' # PNG文件头的一部分
with open('test_files/binary_write.bin', 'wb') as f:
bytes_written = f.write(binary_data)
print(f"写入了 {bytes_written} 个字节")
# 读取并验证
with open('test_files/binary_write.bin', 'rb') as f:
data = f.read()
print(f"读取的二进制数据: {data}")
print(f"十六进制表示: {data.hex()}")
缓冲区与flush()¶
import time
# 演示缓冲区的作用
print("演示缓冲区机制:")
with open('test_files/buffer_demo.txt', 'w', encoding='utf-8') as f:
f.write('第一行\n')
print("写入第一行,但可能还在缓冲区中")
time.sleep(1)
f.write('第二行\n')
f.flush() # 强制刷新缓冲区
print("写入第二行并刷新缓冲区")
time.sleep(1)
f.write('第三行\n')
print("写入第三行,文件关闭时会自动刷新")
print("\n最终文件内容:")
with open('test_files/buffer_demo.txt', 'r', encoding='utf-8') as f:
print(f.read())
6.4 文件指针与定位¶
文件指针的概念¶
文件指针是一个标记,指示当前读取或写入的位置。每次读取或写入操作后,文件指针会自动移动。
tell()方法¶
tell() 方法返回当前文件指针的位置:
# 演示tell()方法
with open('test_files/numbers.txt', 'r') as f:
print(f"初始位置: {f.tell()}")
data = f.read(5)
print(f"读取5个字符: {repr(data)}")
print(f"当前位置: {f.tell()}")
输出结果:
初始位置: 0
读取5个字符: '01234'
当前位置: 5
seek()方法¶
seek() 方法用于移动文件指针:
with open('test_files/numbers.txt', 'r') as f:
# seek(offset, whence)
# whence: 0=从文件开头, 1=从当前位置, 2=从文件末尾
print(f"初始位置: {f.tell()}")
data = f.read(5)
print(f"读取5个字符: {repr(data)}")
print(f"当前位置: {f.tell()}")
# 移动到文件开头
f.seek(0)
print(f"重置后位置: {f.tell()}")
print(f"重新读取: {repr(f.read(10))}")
输出结果:
初始位置: 0
读取5个字符: '01234'
当前位置: 5
重置后位置: 0
重新读取: '0123456789'
文件的随机访问¶
# 创建一个结构化的数据文件
data_records = [
"Record001:Alice:25\n",
"Record002:Bob:30\n",
"Record003:Charlie:35\n",
"Record004:David:40\n"
]
with open('test_files/records.txt', 'w', encoding='utf-8') as f:
f.writelines(data_records)
# 随机访问特定记录
def read_record(filename, record_num):
"""
读取指定编号的记录
Args:
filename: 文件名
record_num: 记录编号(从1开始)
"""
record_length = len(data_records[0]) # 假设每条记录长度相同
with open(filename, 'r', encoding='utf-8') as f:
# 计算目标记录的位置
position = (record_num - 1) * record_length
f.seek(position)
record = f.read(record_length).strip()
return record
# 测试随机访问
print("随机访问记录:")
for i in [3, 1, 4, 2]:
record = read_record('test_files/records.txt', i)
print(f"记录{i}: {record}")
文本模式与二进制模式的区别¶
# 在文本模式下,seek()的行为可能不同
with open('test_files/text_vs_binary.txt', 'w', encoding='utf-8') as f:
f.write('Hello\nWorld\n中文测试')
print("文本模式下的指针操作:")
with open('test_files/text_vs_binary.txt', 'r', encoding='utf-8') as f:
print(f"开始位置: {f.tell()}")
print(f"读取5个字符: '{f.read(5)}'")
print(f"当前位置: {f.tell()}")
print("\n二进制模式下的指针操作:")
with open('test_files/text_vs_binary.txt', 'rb') as f:
print(f"开始位置: {f.tell()}")
print(f"读取5个字节: {f.read(5)}")
print(f"当前位置: {f.tell()}")
6.5 with语句与上下文管理¶
with语句的概念¶
with 语句是Python中处理上下文管理的推荐方式。它确保资源在使用后能够正确释放,即使在发生异常的情况下也是如此。
上下文管理器¶
上下文管理器是支持上下文管理协议的对象,它定义了在 with 语句中使用的运行时上下文。
# 传统方式 vs with语句
print("传统方式(不推荐):")
try:
f = open('test_files/sample.txt', 'r', encoding='utf-8')
content = f.read()
print("文件内容:", content[:20] + "...")
finally:
f.close()
print("文件已关闭")
print("\nwith语句方式(推荐):")
with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
content = f.read()
print("文件内容:", content[:20] + "...")
print(f"在with块中,文件是否关闭: {f.closed}")
print(f"离开with块后,文件是否关闭: {f.closed}")
输出结果:
传统方式(不推荐):
文件内容: Hello, World!
This i...
文件已关闭
with语句方式(推荐):
文件内容: Hello, World!
This i...
在with块中,文件是否关闭: False
离开with块后,文件是否关闭: True
with语句的语法¶
# 基本语法
with open('test_files/with_demo.txt', 'w', encoding='utf-8') as file:
file.write('使用with语句写入的内容')
# 文件会在离开with块时自动关闭
# 验证文件内容
with open('test_files/with_demo.txt', 'r', encoding='utf-8') as file:
print("文件内容:", file.read())
with语句的优势¶
1. 自动资源管理¶
# 即使发生异常,文件也会被正确关闭
try:
with open('test_files/exception_test.txt', 'w', encoding='utf-8') as f:
f.write('开始写入')
# 模拟异常
raise ValueError("模拟异常")
f.write('这行不会被执行')
except ValueError as e:
print(f"捕获异常: {e}")
print(f"文件是否已关闭: {f.closed}")
2. 代码简洁¶
# 读取多个文件并比较内容
files_to_compare = ['test_files/sample.txt', 'test_files/write_demo.txt']
for filename in files_to_compare:
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
print(f"{filename}: {len(content)} 个字符")
同时操作多个文件¶
方法1:嵌套with语句¶
with open('test_files/source.txt', 'w', encoding='utf-8') as source:
source.write('源文件内容\n第二行\n第三行')
with open('test_files/source.txt', 'r', encoding='utf-8') as source:
with open('test_files/destination.txt', 'w', encoding='utf-8') as dest:
# 复制文件内容
for line in source:
dest.write(line.upper()) # 转换为大写
print("复制并转换后的内容:")
with open('test_files/destination.txt', 'r', encoding='utf-8') as f:
print(f.read())
方法2:逗号分隔语法¶
# 同时打开多个文件
with open('test_files/file1.txt', 'w', encoding='utf-8') as f1, \
open('test_files/file2.txt', 'w', encoding='utf-8') as f2:
f1.write('文件1的内容')
f2.write('文件2的内容')
print("同时写入两个文件")
# 验证结果
with open('test_files/file1.txt', 'r', encoding='utf-8') as f1, \
open('test_files/file2.txt', 'r', encoding='utf-8') as f2:
print(f"文件1: {f1.read()}")
print(f"文件2: {f2.read()}")
自定义上下文管理器简介¶
class FileManager:
"""
自定义文件管理器上下文管理器
"""
def __init__(self, filename, mode, encoding='utf-8'):
self.filename = filename
self.mode = mode
self.encoding = encoding
self.file = None
def __enter__(self):
"""进入with块时调用"""
print(f"打开文件: {self.filename}")
self.file = open(self.filename, self.mode, encoding=self.encoding)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""离开with块时调用"""
if self.file:
self.file.close()
print(f"关闭文件: {self.filename}")
if exc_type:
print(f"处理异常: {exc_type.__name__}: {exc_val}")
return False # 不抑制异常
# 使用自定义上下文管理器
with FileManager('test_files/custom_manager.txt', 'w') as f:
f.write('使用自定义上下文管理器')
6.6 目录操作¶
os模块简介¶
os 模块提供了与操作系统交互的功能,包括文件和目录操作。
import os
print("当前Python版本的os模块功能演示")
print(f"操作系统类型: {os.name}")
print(f"当前工作目录: {os.getcwd()}")
输出结果:
当前Python版本的os模块功能演示
操作系统类型: nt
当前工作目录: C:\Users\yeyupiaoling/Python从入门到精通\test_files
目录相关操作¶
import os
# 获取当前工作目录
current_dir = os.getcwd()
print(f"当前工作目录: {current_dir}")
# 列出目录内容
print("目录内容:")
for item in os.listdir('.'):
print(f" {item}")
# 创建目录
test_dir = 'test_subdir'
if not os.path.exists(test_dir):
os.mkdir(test_dir)
print(f"创建目录: {test_dir}")
else:
print(f"目录已存在: {test_dir}")
print(f"检查目录是否存在: {os.path.exists(test_dir)}")
print(f"是否为目录: {os.path.isdir(test_dir)}")
输出结果:
当前工作目录: C:\Users\yeyupiaoling/Python从入门到精通\test_files
目录内容: ['binary_sample.bin', 'numbers.txt', 'output.txt', 'sample.txt', 'test_dir_ops.py', 'test_file_ops.py']
创建目录: test_subdir
检查目录是否存在: True
是否为目录: True
路径操作¶
import os.path
# 路径拼接
file_path = os.path.join('test_subdir', 'test.txt')
print(f"拼接路径: {file_path}")
print(f"目录名: {os.path.dirname(file_path)}")
print(f"文件名: {os.path.basename(file_path)}")
print(f"分离路径: {os.path.split(file_path)}")
print(f"分离扩展名: {os.path.splitext(file_path)}")
输出结果:
拼接路径: test_subdir\test.txt
目录名: test_subdir
文件名: test.txt
分离路径: ('test_subdir', 'test.txt')
分离扩展名: ('test_subdir\\test', '.txt')
pathlib模块(Python 3.4+)¶
pathlib 提供了面向对象的路径操作方式:
from pathlib import Path
# 创建Path对象
path = Path('.')
print(f"当前路径: {path.absolute()}")
print(f"路径存在: {path.exists()}")
print(f"是否为目录: {path.is_dir()}")
# 创建文件
test_file = Path('test_subdir') / 'pathlib_test.txt'
test_file.write_text('Hello from pathlib!', encoding='utf-8')
print(f"创建文件: {test_file}")
print(f"文件内容: {test_file.read_text(encoding='utf-8')}")
输出结果:
当前路径: C:\Users\yeyupiaoling/Python从入门到精通\test_files
路径存在: True
是否为目录: True
创建文件: test_subdir\pathlib_test.txt
文件内容: Hello from pathlib!
文件和目录的遍历¶
使用os.walk()函数¶
from pathlib import Path
print("当前目录下的所有文件:")
for item in Path('.').iterdir():
if item.is_file():
print(f" 文件: {item.name}")
elif item.is_dir():
print(f" 目录: {item.name}/")
输出结果:
当前目录下的所有文件:
文件: binary_sample.bin
文件: numbers.txt
文件: output.txt
文件: sample.txt
文件: test_dir_ops.py
文件: test_file_ops.py
目录: test_subdir/
使用glob模块¶
import glob
# 创建一些测试文件
test_extensions = ['.txt', '.py', '.md']
for ext in test_extensions:
filename = f'test_files/demo{ext}'
with open(filename, 'w') as f:
f.write(f'这是一个{ext}文件')
print("\n使用glob模块查找文件:")
# 查找所有.txt文件
txt_files = glob.glob('test_files/*.txt')
print(f"所有.txt文件: {txt_files}")
# 查找所有文件
all_files = glob.glob('test_files/*')
print(f"所有文件: {len(all_files)} 个")
# 递归查找
all_txt_recursive = glob.glob('test_files/**/*.txt', recursive=True)
print(f"递归查找所有.txt文件: {all_txt_recursive}")
# 使用pathlib的glob
from pathlib import Path
path = Path('test_files')
py_files = list(path.glob('*.py'))
print(f"使用pathlib查找.py文件: {py_files}")
实际应用示例¶
def organize_files_by_extension(directory):
"""
按文件扩展名组织文件
Args:
directory: 要组织的目录路径
"""
from collections import defaultdict
import os
file_groups = defaultdict(list)
# 遍历目录中的所有文件
for filename in os.listdir(directory):
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
_, ext = os.path.splitext(filename)
ext = ext.lower() or 'no_extension'
file_groups[ext].append(filename)
# 显示结果
print(f"\n目录 '{directory}' 中的文件分类:")
for ext, files in file_groups.items():
print(f"{ext}: {len(files)} 个文件")
for file in files[:3]: # 只显示前3个
print(f" - {file}")
if len(files) > 3:
print(f" ... 还有 {len(files) - 3} 个文件")
# 使用文件组织功能
organize_files_by_extension('test_files')
总结¶
本章详细介绍了Python中文件操作的各个方面:
-
文件的打开与关闭:学习了
open()函数的各种参数和模式,以及正确关闭文件的重要性。 -
文件读取方法:掌握了
read()、readline()、readlines()等方法,以及处理大文件的策略。 -
文件写入操作:了解了
write()和writelines()方法,以及不同写入模式的区别。 -
文件指针与定位:学习了使用
tell()和seek()方法进行文件随机访问。 -
with语句:掌握了上下文管理器的使用,这是处理文件操作的最佳实践。
-
目录操作:学习了使用
os模块和pathlib模块进行目录和路径操作。
最佳实践建议¶
-
始终使用with语句:确保文件能够正确关闭,即使发生异常也不例外。
-
指定编码格式:处理文本文件时,明确指定编码格式(推荐UTF-8)。
-
处理大文件时使用生成器:避免一次性加载整个文件到内存。
-
使用pathlib进行路径操作:它提供了更现代、更直观的API。
-
异常处理:在文件操作中适当使用try-except处理可能的异常。
通过掌握这些文件操作技能,你将能够高效地处理各种文件相关的任务,为后续的数据处理、配置管理等工作打下坚实的基础。