第16课:Python|文件操作基础【文本文件读写、open函数与上下文管理器with】

在这里插入图片描述


📖 开篇导读

到目前为止,我们写的所有程序都是在内存中处理数据——变量在程序运行时存在,关闭程序后就会消失。这就像在白板上写字,一擦就没。但很多场景下,我们需要持久化存储数据:用户的配置信息、程序的日志、处理后的报表、爬虫抓取的内容……这些都需要保存到文件中。

Python提供了简单而强大的内置函数open()来进行文件读写。配合上下文管理器with,可以安全、优雅地操作文件,无需担心忘记关闭。

💡 工作场景:后端程序需要读取配置文件;数据分析师读取CSV文件进行处理;日志模块将运行信息写入文件;爬虫将抓取的HTML保存到本地;自动化脚本处理Excel和文本……文件操作是每个程序员的必修课。

本课我们将学习:

  • 文件的打开模式(读、写、追加、二进制等)
  • 读取文件的各种方法
  • 写入文件的多种方式
  • 上下文管理器with的使用
  • 文件指针与定位
  • 实战:文件复制、统计行数、敏感词过滤

学完本课,你将能自如地处理文本文件,实现数据的持久化存储。


🎯 学习目标

目标编号具体掌握内容对应面试/工作价值
1️⃣掌握open()函数的模式和编码参数正确打开各种文件
2️⃣熟练使用读取方法 (read, readline, readlines)按需读取文件内容
3️⃣熟练使用写入方法 (write, writelines)保存数据到文件
4️⃣理解文件指针概念及seek()/tell()随机访问文件
5️⃣掌握上下文管理器with,自动关闭文件避免资源泄露
6️⃣了解常见异常及处理(如FileNotFoundError编写健壮的文件操作

🔥 面试考点:“open文件的几种模式?”“readreadlinereadlines的区别?”“为什么要用with打开文件?”“如何逐行读取大文件?”


📚 知识点理论精讲

一、文件的基本概念与打开模式

1.1 什么是文件

文件是存储在外部介质(如硬盘)上的数据集合。Python通过文件对象与操作系统进行交互,完成读写操作。

1.2 open()函数

语法:

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

最常用的参数:

  • file:文件路径(字符串)
  • mode:打开模式,默认'r'(只读文本)
  • encoding:编码方式,通常'utf-8'

1.3 文件打开模式详解

模式说明文件指针位置文件不存在时
'r'只读(默认)开头抛出FileNotFoundError
'w'只写(会覆盖原文件)开头创建新文件
'a'追加(末尾添加)末尾创建新文件
'x'独占创建,如果文件已存在则报错开头创建新文件
'b'二进制模式(与r/w/a等组合,如'rb'依据主模式同上
't'文本模式(默认,可与r/w/a组合,如'rt'依据主模式同上
'+'读写模式(如'r+'依据主模式依据主模式

常用组合:

  • 'r':只读文本(最常用)
  • 'w':只写文本,覆盖已有内容
  • 'a':追加文本
  • 'rb':读取二进制文件(如图片、音频)
  • 'wb':写入二进制文件
  • 'r+':读写(指针在开头)
  • 'w+':读写,覆盖或创建
  • 'a+':读和追加,指针在末尾

🧠 通俗类比:打开文件就像打开一本笔记本。

  • 'r'(只读):你只能看,不能写。如果笔记本不存在,会报错。
  • 'w'(只写):你可以写,但会先把原来内容清空(如果笔记本存在)或新建一本。
  • 'a'(追加):你在最后面续写,不会破坏已有内容。
  • 'r+':你既可以看,也可以修改已有内容。

1.4 编码问题

文本文件有编码(如UTF-8、GBK)。Python默认使用平台编码(Windows上通常是GBK,Mac/Linux是UTF-8)。为了避免乱码,建议显式指定encoding='utf-8'

# 正确方式
f = open('data.txt', 'r', encoding='utf-8')

二、读取文件的方法

2.1 read(size=-1)

读取指定字节数(文本模式下是字符数)。不传参数或负数,读取整个文件。

with open('data.txt', 'r', encoding='utf-8') as f:
    content = f.read()        # 读取全部
    print(content)

注意:大文件不要使用read()不带参数,会耗尽内存。应逐行或分块读取。

2.2 readline()

读取一行(包括换行符\n)。到达文件末尾时返回空字符串。

with open('data.txt', 'r', encoding='utf-8') as f:
    line = f.readline()
    while line:
        print(line.strip())
        line = f.readline()

2.3 readlines()

读取所有行,返回字符串列表(每行包括换行符)。

with open('data.txt', 'r', encoding='utf-8') as f:
    lines = f.readlines()
    for line in lines:
        print(line, end='')

2.4 直接迭代文件对象(推荐

文件对象本身是可迭代的,可以直接for line in f:逐行读取,内存友好。

with open('data.txt', 'r', encoding='utf-8') as f:
    for line in f:        # 逐行读取,不会一次加载整个文件
        print(line.strip())

💡 工作应用:处理大型日志文件时,必须使用逐行迭代,而不能用read()


三、写入文件的方法

3.1 write(str)

将字符串写入文件。返回写入的字符数(可用于校验)。不会自动添加换行符

with open('output.txt', 'w', encoding='utf-8') as f:
    f.write("Hello\n")
    f.write("World\n")

3.2 writelines(lines)

写入一个字符串列表,也不会自动添加换行符。

lines = ["第一行\n", "第二行\n", "第三行\n"]
with open('output.txt', 'w', encoding='utf-8') as f:
    f.writelines(lines)

3.3 追加模式

使用'a'模式,在文件末尾追加内容。

with open('log.txt', 'a', encoding='utf-8') as f:
    f.write("新的日志条目\n")

3.4 写入非字符串数据

需要先转换为字符串。

data = [1, 2, 3]
with open('nums.txt', 'w') as f:
    f.write(str(data))   # 写入 "[1, 2, 3]"

四、上下文管理器:with语句

4.1 为什么需要with

传统打开文件需要手动关闭,容易遗漏导致资源泄露。

# 不推荐
f = open('data.txt', 'r')
content = f.read()
f.close()   # 容易忘记

如果read()过程中发生异常,close()可能不会执行。

4.2 with的作用

with语句创建一个上下文环境,进入时执行__enter__(打开文件),退出时自动执行__exit__(关闭文件),即使发生异常也会关闭。

with open('data.txt', 'r', encoding='utf-8') as f:
    content = f.read()
# 此处文件已自动关闭,无需手动close

最佳实践:始终使用with打开文件。

4.3 多个文件的with

with open('input.txt', 'r') as f_in, open('output.txt', 'w') as f_out:
    data = f_in.read()
    f_out.write(data.upper())

五、文件指针与随机访问

文件指针指示当前读写位置。可以使用tell()获取当前位置,seek()移动指针。

5.1 tell():返回当前指针位置(字节数)

with open('data.txt', 'r', encoding='utf-8') as f:
    print(f.tell())   # 0
    f.read(5)
    print(f.tell())   # 5(如果每个字符1字节)

5.2 seek(offset, whence)

移动指针到指定位置。

  • whence=0(默认):相对于文件开头
  • whence=1:相对于当前位置
  • whence=2:相对于文件末尾
with open('data.txt', 'r', encoding='utf-8') as f:
    f.seek(10)           # 移动到第10字节
    f.seek(5, 1)         # 当前位置往后5字节
    f.seek(-10, 2)       # 从末尾往前10字节

⚠️ 注意:文本模式下,seek的偏移量必须是由tell()返回的位置(因为编码字符长度不固定)。随机访问建议使用二进制模式。


六、常见异常处理

异常触发条件处理方式
FileNotFoundError'r'模式打开不存在的文件检查路径或先创建
PermissionError无权限访问文件修改权限或换路径
UnicodeDecodeError编码不匹配指定正确的encoding
IsADirectoryError试图打开目录作为文件检查路径是否为文件
try:
    with open('missing.txt', 'r') as f:
        content = f.read()
except FileNotFoundError:
    print("文件不存在,请检查路径")
except PermissionError:
    print("没有读取权限")

七、二进制文件操作

对于图片、音频、视频等非文本文件,使用二进制模式'rb''wb'

# 复制图片
with open('source.jpg', 'rb') as src:
    data = src.read()

with open('copy.jpg', 'wb') as dst:
    dst.write(data)

二进制模式不需要指定编码。


💻 代码案例实操

案例1:基础文件读写——写一首诗并读取

"""
poem_file.py
演示基础的写文件和读文件
"""

# 写入文件
poem = """床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。"""

with open('poem.txt', 'w', encoding='utf-8') as f:
    f.write(poem)
print("诗歌已写入 poem.txt")

# 读取整个文件
with open('poem.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print("\n=== 完整内容 ===")
    print(content)

# 逐行读取
print("\n=== 逐行读取 ===")
with open('poem.txt', 'r', encoding='utf-8') as f:
    for line in f:
        # strip() 去掉每行末尾的换行符
        print(f"行内容: {line.strip()}")

案例2:文件追加与日志记录

"""
log_writer.py
模拟日志写入,演示追加模式
"""

import datetime

def write_log(message, log_file='app.log'):
    """将带时间戳的消息追加到日志文件"""
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(log_file, 'a', encoding='utf-8') as f:
        f.write(f"[{timestamp}] {message}\n")

# 模拟多个日志
write_log("程序启动")
write_log("用户登录成功")
write_log("执行任务A")
write_log("程序正常退出")

# 读取并显示日志
print("=== 当前日志内容 ===")
with open('app.log', 'r', encoding='utf-8') as f:
    for line in f:
        print(line, end='')

案例3:读取大文件的正确姿势(逐行迭代)

"""
large_file_reader.py
演示如何高效读取大文件(避免内存爆炸)
"""

def count_lines(filename):
    """统计文件行数(逐行读取,不占内存)"""
    count = 0
    with open(filename, 'r', encoding='utf-8') as f:
        for line in f:   # 逐行迭代
            count += 1
    return count

def search_keyword(filename, keyword):
    """搜索包含关键词的行"""
    matches = []
    with open(filename, 'r', encoding='utf-8') as f:
        for line_no, line in enumerate(f, 1):
            if keyword in line:
                matches.append((line_no, line.strip()))
    return matches

# 模拟处理大文件(如果文件很大,不会全部读入内存)
# 实际使用时,可以传入大型日志文件
if __name__ == "__main__":
    # 先创建一个小测试文件
    with open('test_large.txt', 'w', encoding='utf-8') as f:
        for i in range(1000):
            f.write(f"这是第{i}行,内容是一些文本。\n")
    
    line_count = count_lines('test_large.txt')
    print(f"总行数: {line_count}")
    
    results = search_keyword('test_large.txt', '500')
    print(f"找到 {len(results)} 行包含'500':")
    for line_no, line in results[:3]:
        print(f"  行{line_no}: {line}")

案例4:文件复制与异常处理

"""
file_copy.py
实现文件复制功能,并处理常见异常
"""

import os

def copy_file(src, dst):
    """复制文件从 src 到 dst"""
    try:
        # 检查源文件是否存在
        if not os.path.exists(src):
            raise FileNotFoundError(f"源文件不存在: {src}")
        
        # 以二进制模式读取和写入,支持任何类型文件
        with open(src, 'rb') as f_src:
            data = f_src.read()
        
        with open(dst, 'wb') as f_dst:
            f_dst.write(data)
        
        print(f"复制成功: {src} -> {dst}")
        return True
        
    except FileNotFoundError as e:
        print(f"错误: {e}")
    except PermissionError as e:
        print(f"权限错误: {e}")
    except Exception as e:
        print(f"未知错误: {e}")
    
    return False

# 测试
copy_file('poem.txt', 'poem_copy.txt')
copy_file('not_exist.txt', 'copy.txt')

案例5:文件指针操作与tell/seek

"""
file_pointer_demo.py
演示文件指针移动和随机访问
"""

# 准备示例文件
with open('test.txt', 'w', encoding='utf-8') as f:
    f.write("0123456789")
    f.write("ABCDEFGHIJ")

# 读取并观察指针位置
with open('test.txt', 'r', encoding='utf-8') as f:
    print(f"初始位置: {f.tell()}")
    
    data = f.read(5)
    print(f"读取5个字符: '{data}', 当前位置: {f.tell()}")
    
    # 跳到第10个字符处
    f.seek(10)
    print(f"seek(10)后位置: {f.tell()}")
    data2 = f.read(5)
    print(f"读取5个字符: '{data2}'")
    
    # 从当前位置偏移+2
    f.seek(2, 1)   # 1表示相对当前位置
    print(f"seek(2,1)后位置: {f.tell()}")
    data3 = f.read(3)
    print(f"读取3个字符: '{data3}'")
    
    # 从末尾往前5个字节(注意文本模式下谨慎使用)
    # f.seek(-5, 2)  # 可能出错,因为文本模式编码不定长

# 二进制模式下的seek更可靠
with open('test.txt', 'rb') as f:
    f.seek(-5, 2)   # 从末尾往前5个字节
    last_bytes = f.read()
    print(f"最后5字节内容: {last_bytes}")

案例6:配置文件读取与修改

"""
config_reader.py
模拟读取和修改配置文件(类似INI格式)
"""

# 假设有一个配置文件 config.txt
# 格式: key = value

def read_config(filename):
    """读取配置文件,返回字典"""
    config = {}
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith('#'):
                    continue
                if '=' in line:
                    key, value = line.split('=', 1)
                    config[key.strip()] = value.strip()
    except FileNotFoundError:
        print(f"配置文件 {filename} 不存在,将创建新文件")
    return config

def write_config(filename, config):
    """将配置字典写入文件"""
    with open(filename, 'w', encoding='utf-8') as f:
        f.write("# 配置文件\n")
        for key, value in config.items():
            f.write(f"{key} = {value}\n")
    print(f"配置已保存到 {filename}")

# 使用示例
config_file = 'myapp.ini'

# 读取现有配置
config = read_config(config_file)
print("当前配置:", config)

# 修改或添加配置
config['username'] = 'admin'
config['debug'] = 'true'
config['timeout'] = '30'

# 写回文件
write_config(config_file, config)

# 再次读取验证
new_config = read_config(config_file)
print("更新后配置:", new_config)

案例7:多文件同时操作——合并文件

"""
file_merger.py
合并多个文本文件的内容到一个文件
"""

def merge_files(input_files, output_file):
    """将多个文件按顺序合并到一个输出文件"""
    with open(output_file, 'w', encoding='utf-8') as out_f:
        for file_path in input_files:
            try:
                with open(file_path, 'r', encoding='utf-8') as in_f:
                    out_f.write(f"--- 文件: {file_path} ---\n")
                    out_f.write(in_f.read())
                    out_f.write("\n\n")  # 间隔
                print(f"已合并: {file_path}")
            except FileNotFoundError:
                print(f"警告: 文件 {file_path} 不存在,跳过")

# 准备测试文件
for i in range(1, 4):
    with open(f'part{i}.txt', 'w', encoding='utf-8') as f:
        f.write(f"这是第{i}部分的内容。\n" * 3)

# 合并
merge_files(['part1.txt', 'part2.txt', 'part3.txt'], 'merged.txt')

# 查看结果
print("\n=== 合并后的文件内容 ===")
with open('merged.txt', 'r', encoding='utf-8') as f:
    print(f.read())

⚠️ 易错点避坑总结

序号坑点描述后果解决方案
1忘记指定encoding,导致跨平台兼容问题中文乱码或UnicodeDecodeError明确使用encoding='utf-8'
2使用'w'模式时误删原有内容文件被覆盖,数据丢失需要保留原内容用'a'或先读取再写入
3大文件用read()不带参数内存溢出逐行迭代或分块读取
4忘记关闭文件(不用with资源泄露,可能写不完整始终使用with语句
5文本模式下使用seek随机访问位置错误或因编码报错二进制模式使用seek,文本模式少用
6写入时忘记添加换行符\n所有内容挤在一行显式添加\n或使用print(file=f)
7路径中使用反斜杠\转义错误(如\t\n使用原始字符串r"path"或正斜杠/
8异常处理过于宽泛隐藏其他错误捕获具体异常(如FileNotFoundError
9同时读写同一个文件时未使用+模式写入后读不到新内容(指针位置问题)正确使用seek或分开操作
10二进制文件写入字符串(忘记encodeTypeError二进制模式写入bytes,或使用wb+encode

📝 课后实战练习题

第1题:基本读写

编写程序,创建一个todo.txt文件,写入以下内容:

买菜
写报告
学习Python

然后读取并逐行打印,每行前显示行号(从1开始)。

第2题:日志分析

假设有一个access.log文件,每行格式:时间 IP 请求方法 状态码。编写程序:

  • 统计状态码为404的行数
  • 找出所有IP地址(去重)
  • 输出次数最多的IP地址

(你可以先创建一个小型测试日志文件,包含10-20行模拟数据)

第3题:文件追加与搜索

编写一个电话本管理程序:

  • 支持添加联系人(姓名,电话)到contacts.txt,格式:姓名|电话
  • 支持根据姓名查找并显示电话
  • 支持删除联系人(先读取全部,删除匹配行后再写回)

第4题:大文件处理

生成一个包含100000行的大文件(每行一个随机整数),然后编写程序:

  • 统计文件中所有数字的总和
  • 找出最大值和最小值
  • 要求使用逐行读取,不能一次性读入内存

第5题:复制二进制文件

编写一个函数copy_binary(src, dst, chunk_size=1024),使用分块读写(每次读取chunk_size字节)来复制文件,适用于任意大小文件。对比一次性read()和分块读写的效率(可选)。

第6题:CSV格式读取

假设有一个students.csv文件,内容如下:

姓名,年龄,分数
张三,18,85
李四,19,92
王五,18,78

编写程序读取该文件,将每行转换为字典(如{"姓名":"张三","年龄":18,"分数":85}),然后计算平均分数。

第7题:文件加密简单版

编写一个程序,读取文本文件plain.txt,对每个字符进行简单的移位加密(如每个字母后移3位,非字母不变),将加密结果写入encrypted.txt。再编写一个解密程序,将encrypted.txt恢复为原内容。

🔜 下节课预告

掌握了文本文件的基础操作后,下一节课我们将深入文件处理的进阶内容:二进制文件、路径处理以及批量文件操作。

第17课:文件进阶:二进制文件、路径处理与批量文件操作实战

内容包括:

  • 二进制文件的读写(图片、音频、视频)
  • ospathlib模块处理路径
  • 文件和目录的创建、删除、重命名
  • globos.walk批量遍历文件
  • 实战:批量重命名文件、按扩展名分类、自动备份

文件操作与系统交互是自动化脚本的核心,学完你将能编写实用的文件管理工具。

🌟 学习鼓励:文件操作是编程中非常实用的技能。你可能会遇到各种编码错误、路径问题,但请耐心调试——多练习几次,你就会掌握其中的规律。特别是with语句,一定要养成习惯。下一课的批量文件操作会让你的工作效率倍增!


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值