第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中文件操作的各个方面:

  1. 文件的打开与关闭:学习了open()函数的各种参数和模式,以及正确关闭文件的重要性。

  2. 文件读取方法:掌握了read()readline()readlines()等方法,以及处理大文件的策略。

  3. 文件写入操作:了解了write()writelines()方法,以及不同写入模式的区别。

  4. 文件指针与定位:学习了使用tell()seek()方法进行文件随机访问。

  5. with语句:掌握了上下文管理器的使用,这是处理文件操作的最佳实践。

  6. 目录操作:学习了使用os模块和pathlib模块进行目录和路径操作。

最佳实践建议

  1. 始终使用with语句:确保文件能够正确关闭,即使发生异常也不例外。

  2. 指定编码格式:处理文本文件时,明确指定编码格式(推荐UTF-8)。

  3. 处理大文件时使用生成器:避免一次性加载整个文件到内存。

  4. 使用pathlib进行路径操作:它提供了更现代、更直观的API。

  5. 异常处理:在文件操作中适当使用try-except处理可能的异常。

通过掌握这些文件操作技能,你将能够高效地处理各种文件相关的任务,为后续的数据处理、配置管理等工作打下坚实的基础。

小夜