尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python文件操作100题精讲:从基础读写到实战应用

Python文件操作100题精讲:从基础读写到实战应用 1. 项目概述为什么文件操作是Python编程的“基本功”如果你刚开始学Python可能觉得变量、循环、判断这些语法已经够用了。但当你真正想用Python做点实事比如分析一份日志、处理一批图片、或者保存你的爬虫数据时你很快就会撞上一堵墙文件。没错无论是读取一个配置文件还是把计算结果写入CSV文件操作都是连接你的代码和外部世界或者说连接内存和硬盘的唯一桥梁。它不像算法那样炫酷但却是每个实用程序都绕不开的“基础设施”。我见过不少新手语法学得挺溜一到读写文件就懵了路径不对、编码错误、忘记关闭文件导致数据丢失……各种问题层出不穷。这个“Python基础练习100题”的第二篇专门聚焦文件操作目的就是把这块“硬骨头”啃下来。它不仅仅是教你几个函数而是通过100个由浅入深的练习题让你在反复的“犯错-纠正”过程中建立起对文件操作的肌肉记忆和条件反射。从最基础的open()、read()、write()到处理不同编码的文本文件、读写结构化的CSV/JSON再到遍历目录、处理二进制文件这套练习几乎覆盖了日常开发中90%的文件处理场景。掌握了这些你写的Python脚本才真正具备了“生产力”能从简单的计算器进化成能处理真实数据的工具。2. 核心思路与练习框架设计这套练习的设计逻辑非常清晰遵循了“单一知识点突破 - 知识点组合 - 解决实际问题”的递进路线。它不是一股脑地把所有文件函数扔给你而是像搭积木一样一层层构建你的知识体系。2.1 练习设计的四个阶段第一阶段是基础读写操作。这个阶段的目标是让你彻底搞懂open()函数的各种模式r,w,a,r,rb,wb等理解文件对象的基本方法read(),readline(),readlines(),write(),writelines()。练习会从“读取文件全部内容并打印”开始然后过渡到“逐行读取并处理”再到“向文件追加内容”。这个阶段的关键是理解文件指针的概念以及不同模式下打开文件对原有内容的影响。例如用w模式打开一个已存在的文件会立即清空它这是一个非常容易踩坑的地方。第二阶段是编码与异常处理。文本文件读写最大的“暗礁”就是编码。练习会强制你处理不同编码的文件如UTF-8, GBK, ASCII让你熟悉encoding参数并学会使用try...except来捕获UnicodeDecodeError等异常。一个经典的练习是尝试用UTF-8读取一个GBK编码的文件捕获异常后再用正确的编码重新读取。这能让你深刻体会到“指定正确的编码”不是建议而是必须。第三阶段是结构化文件与目录操作。掌握了纯文本读写后练习会引入csv和json这两个标准库教你如何读写表格数据和结构化数据。同时会结合os和pathlib模块进行目录遍历、文件查找、路径拼接等操作。例如“找出某个目录下所有扩展名为.txt的文件并统计每个文件的行数”就是一个典型的综合练习。第四阶段是高级应用与综合挑战。这一部分会模拟真实场景比如日志文件分析、配置文件解析、简单数据清洗等。也可能涉及二进制文件的简单操作如图片文件头读取或使用with语句进行上下文管理的最佳实践。目标是让你把前三个阶段学到的技能串联起来解决一个完整的小问题。2.2 为什么是100题量变引起质变可能有人会觉得100题太多了。但根据我的经验文件操作中的很多“坑”光靠看文档或理解概念是记不住的必须亲手掉进去一次再爬出来印象才深刻。比如你是否知道read()和readlines()在读取大文件时可能导致内存溢出而使用for line in file:这种迭代方式则是内存友好的。这种细微的差别只有在反复练习和对比中才能内化。100道题提供了足够的重复和变体确保每个核心概念都能以多种形式出现。当你做到第50题时打开文件、处理编码、关闭文件已经成了本能反应做到第80题你会开始不自觉地思考如何用pathlib写出更优雅的路径代码做到最后面对一个“分析本周日志错误率”的需求你脑子里会自然浮现出步骤和需要用到的模块。注意练习时切忌直接复制答案。务必自己动手敲每一行代码并尝试用不同的方法去实现同一个需求。比如读取文件全部内容你可以用read()也可以用readlines()拼接甚至可以自己写循环。比较它们的差异是提升理解的关键。3. 核心细节解析与避坑指南文件操作看似简单但魔鬼藏在细节里。下面我结合常见的练习题类型拆解几个最容易出错的核心细节并分享一些教科书里不会写的“避坑心得”。3.1 文件打开模式不只是“读”和“写”open()函数的模式参数是第一个门槛。很多人只记得r和w但实际场景复杂得多。r(只读)默认模式。文件必须存在否则抛FileNotFoundError。这是最安全的方式。w(只写)极具破坏性如果文件存在会立即清空如果不存在则创建。新手最大的数据丢失陷阱就源于此。当你只是想添加内容时绝对不要用w。a(追加)在文件末尾追加内容。文件不存在则创建。这是添加日志、记录数据时的首选。r(读写)文件必须存在。指针在开头可读可写。但写操作会覆盖当前位置的字节。如果你读取了前10个字节那么写入会从第11个字节开始覆盖这很容易导致文件内容混乱需要谨慎使用。w(写读)同样会清空文件然后可读可写。用途较少。a(追加读)指针在文件末尾写操作总是追加。可以读取全部内容需用seek(0)将指针移回开头。避坑心得1除非明确要创建新文件或覆盖旧文件否则优先使用r和a。在动手写w之前停顿一秒问自己“我真的不需要原来的数据了吗”3.2 文件编码乱码的万恶之源中文环境下编码问题几乎无法避免。open()的encoding参数是你的救星。UTF-8现代应用和Web数据的标准跨平台兼容性最好。无特殊要求时一律使用encodingutf-8。GBK (GB2312)在Windows系统下创建的某些中文文本文件可能使用此编码。如果你用UTF-8去读一个GBK文件就会遇到UnicodeDecodeError。如何处理未知编码的文件一个实用的技巧是使用chardet库第三方需安装pip install chardet进行编码检测。但在练习和简单脚本中更常见的做法是“试错”import sys file_path unknown.txt encodings_to_try [utf-8, gbk, gb2312, latin-1] # latin-1不会解码失败但可能得到乱码 for enc in encodings_to_try: try: with open(file_path, r, encodingenc) as f: content f.read() print(fSuccessfully read with encoding: {enc}) # 处理content... break except UnicodeDecodeError: continue避坑心得2永远明确指定encoding参数。即使文件是英文的指定utf-8也是最安全的做法。打开文件时把with open(file, r, encodingutf-8) as f:作为你的标准开头模板。3.3 文件路径相对与绝对的“迷宫”另一个常见错误是文件找不到FileNotFoundError这通常是因为路径问题。相对路径相对于当前Python脚本运行的工作目录。如果你在/home/user下运行脚本script.py脚本中的open(data.txt)会在/home/user下寻找data.txt。工作目录可能和你想象的不一样尤其是在IDE中运行或通过其他程序调用时。绝对路径从根目录开始的完整路径如C:\Users\Name\data.txt或/home/user/data.txt。最可靠但移植性差。使用__file__构建可靠路径__file__是当前脚本文件的路径。结合os.path或pathlib可以构建相对于脚本位置的路径这是最推荐的做法。import os from pathlib import Path # 方法1使用 os.path (传统) script_dir os.path.dirname(__file__) # 获取脚本所在目录 data_path os.path.join(script_dir, data, input.txt) # 方法2使用 pathlib (Python3.4更现代、优雅) script_path Path(__file__).resolve() # 获取脚本的绝对路径 data_path script_path.parent / data / input.txt # 使用 / 操作符拼接路径避坑心得3在项目开发中尽量避免使用裸字符串表示路径。使用pathlib来构建、解析和操作路径它能自动处理不同操作系统的路径分隔符/vs\代码更清晰、更安全。3.4 文件关闭与with语句防止资源泄漏早期教程会教你f open(...)然后在最后调用f.close()。但万一在open和close之间发生异常close()可能不会被调用导致文件句柄泄漏在极端情况下一个进程能打开的文件数量是有限的。最佳实践是使用with语句上下文管理器# 错误示范有风险 f open(file.txt, r) content f.read() # 如果这里发生异常f.close()不会执行 f.close() # 正确示范推荐 with open(file.txt, r, encodingutf-8) as f: content f.read() # 退出with块后文件会自动关闭即使发生异常也不例外with语句确保了文件一定会被正确关闭代码也更简洁。在100题的练习中从第一题开始就应该养成这个习惯。4. 关键练习类型与实现详解下面我挑选几种最具代表性的练习题类型给出详细的实现思路和代码解析你可以把这些当作解题的“范式”。4.1 类型一基础内容读取与统计题目示例读取一个文本文件story.txt统计文件的总行数、总单词数以空格分割和总字符数。思路拆解使用with语句安全打开文件。初始化计数器。遍历文件的每一行这是处理大文件的内存友好方式。对每一行更新行数、单词数和字符数。打印结果。代码实现与解析def count_file_stats(file_path): line_count 0 word_count 0 char_count 0 try: with open(file_path, r, encodingutf-8) as f: for line in f: # 逐行迭代内存效率高 line_count 1 # 去除行尾换行符再统计字符更准确 line_without_newline line.rstrip(\n) char_count len(line_without_newline) # 简单的单词分割按空格分割 words line_without_newline.split() word_count len(words) except FileNotFoundError: print(f错误文件 {file_path} 未找到。) return except UnicodeDecodeError: print(f错误文件 {file_path} 编码可能不是UTF-8。) return print(f文件 {file_path} 的统计信息) print(f 行数: {line_count}) print(f 单词数: {word_count}) print(f 字符数不含换行符: {char_count}) # 使用示例 count_file_stats(story.txt)关键点for line in f:直接迭代文件对象每次读一行到内存适合大文件。line.rstrip(\n)去掉行尾的换行符避免将其计入字符数。rstrip只去掉右侧指定字符比strip安全不会误删行首空格。split()默认按任意空白字符空格、制表符等分割这是一个简单的单词定义。更复杂的单词统计可能需要正则表达式。4.2 类型二文件内容过滤与写入题目示例有一个日志文件server.log请筛选出所有包含ERROR关键词的行并将其写入到一个新文件errors.log中。思路拆解以读模式打开源文件。以写模式w或追加模式a打开目标文件。这里用w因为每次运行都想生成一个新的错误日志。遍历源文件的每一行。如果行内包含ERROR注意大小写则将该行写入目标文件。考虑记录一下筛选出的错误行数。代码实现与解析def filter_error_logs(source_file, target_file): error_lines_count 0 try: with open(source_file, r, encodingutf-8) as src, \ open(target_file, w, encodingutf-8) as dst: for line in src: if ERROR in line.upper(): # 转换为大写进行不区分大小写的匹配 dst.write(line) # 写入整行包括原有的换行符 error_lines_count 1 except FileNotFoundError as e: print(f错误找不到文件 - {e}) return except IOError as e: print(f文件读写错误{e}) return print(f筛选完成。共找到 {error_lines_count} 条错误日志已写入 {target_file}。) # 使用示例 filter_error_logs(server.log, errors.log)关键点使用with同时管理多个文件可以用逗号分隔多个open()语句这是Python的语法糖清晰且安全。line.upper()将行内容转为大写这样无论原日志中是ERROR、Error还是error都能被匹配到。这是一种简单的、不区分大小写的过滤方法。dst.write(line)注意line字符串本身已经包含了换行符除非是文件最后一行可能没有所以直接写入即可不需要再加\n。4.3 类型三CSV文件数据处理题目示例有一个学生成绩表grades.csv内容如下name,math,english,python Alice,85,90,88 Bob,78,85,92 Charlie,92,88,95请计算每位学生的平均分并写入一个新的CSV文件grades_avg.csv新增一列average。思路拆解导入csv模块。用csv.reader读取原文件。读取表头并添加新列名average。遍历每一行数据从第二行开始将字符串分数转换为整数计算平均分。用csv.writer写入新文件写入新的表头和数据。代码实现与解析import csv def calculate_average_scores(input_file, output_file): try: with open(input_file, r, encodingutf-8, newline) as infile: # 创建reader对象 reader csv.reader(infile) # 读取表头 header next(reader) # 在表头添加新列 header.append(average) # 准备写入的数据 rows_for_output [header] for row in reader: name row[0] # 将分数部分转换为整数 scores list(map(int, row[1:])) # 使用map进行批量转换 average_score sum(scores) / len(scores) # 保留两位小数 formatted_avg round(average_score, 2) # 构建新行 new_row row [formatted_avg] rows_for_output.append(new_row) # 写入新文件 with open(output_file, w, encodingutf-8, newline) as outfile: writer csv.writer(outfile) writer.writerows(rows_for_output) # 一次性写入所有行 print(f平均分计算完成结果已保存至 {output_file}。) except FileNotFoundError: print(f错误输入文件 {input_file} 未找到。) except ValueError: print(错误CSV文件中包含非数字的分数无法计算。) except Exception as e: print(f发生未知错误{e}) # 使用示例 calculate_average_scores(grades.csv, grades_avg.csv)关键点newline在打开CSV文件时指定newline这是为了防止在Windows系统上出现多余的空行是csv模块官方推荐的做法。csv.reader和csv.writer它们是处理CSV的标准工具比手动用split(,)更可靠因为能正确处理字段内包含逗号或换行符的情况。next(reader)读取迭代器这里是reader的下一个项目即第一行表头。map(int, row[1:])将row列表中从索引1开始的所有字符串通过int函数进行转换返回一个迭代器。list()将其转为列表。writer.writerows()一次性写入多行数据比在循环中多次调用writerow()效率稍高。4.4 类型四目录遍历与文件筛选题目示例找出指定目录./projects下包括子目录所有扩展名为.py的Python文件并列出它们的绝对路径和文件大小以KB为单位。思路拆解使用pathlib的Path对象来表示目录。使用Path.rglob(*.py)方法递归地匹配所有.py文件。rglob比glob更强大能搜索所有子目录。遍历匹配到的路径获取文件大小path.stat().st_size并转换为KB。格式化输出。代码实现与解析from pathlib import Path def find_python_files(directory): dir_path Path(directory) # 检查目录是否存在 if not dir_path.is_dir(): print(f错误{directory} 不是一个有效的目录。) return print(f在目录 {dir_path.resolve()} 中查找 .py 文件) print(- * 60) found_files [] # 使用 rglob 进行递归通配符匹配 for py_file in dir_path.rglob(*.py): # 获取文件大小字节 size_bytes py_file.stat().st_size # 转换为KB保留两位小数 size_kb round(size_bytes / 1024, 2) # 获取绝对路径 abs_path py_file.resolve() found_files.append((abs_path, size_kb)) if not found_files: print(未找到任何 .py 文件。) return # 按文件大小排序可选 found_files.sort(keylambda x: x[1], reverseTrue) for file_path, size_kb in found_files: print(f路径: {file_path}) print(f大小: {size_kb} KB) print(- * 40) print(f\n总计找到 {len(found_files)} 个 .py 文件。) # 使用示例 find_python_files(./projects)关键点Path.rglob(*.py)这是最核心的一行代码。rglob方法支持递归匹配*.py是通配符模式。它会返回一个生成器高效地遍历所有匹配的路径。path.stat().st_sizestat()方法返回文件的状态信息对象st_size属性是文件大小单位字节。path.resolve()获取文件的绝对路径消除任何符号链接的影响。使用pathlib的优势代码比传统的os.walk更简洁、更易读。路径拼接使用/操作符跨平台兼容性好。5. 常见问题与排查技巧实录在实际练习和开发中你会反复遇到一些典型错误。下面这个表格整理了我自己和学员们最常踩的“坑”及其解决方法。问题现象可能原因排查与解决方法FileNotFoundError: [Errno 2] No such file or directory: data.txt1. 文件确实不存在。2. 文件路径错误相对路径基准不对。3. 文件名或扩展名拼写错误如data.txtvsdata.TXT。1.检查文件是否存在在文件管理器或终端中确认。2.打印当前工作目录import os; print(os.getcwd())看是否是预期的目录。3.使用绝对路径或基于__file__的路径这是最可靠的方案。4.注意大小写在Linux/Mac系统下文件名是区分大小写的。UnicodeDecodeError: utf-8 codec cant decode byte ...文件的实际编码与open()函数中指定的encoding参数默认为系统编码不匹配。常见于Windows系统创建的含中文的txt文件GBK编码。1.指定正确的编码尝试encodinggbk或gb2312。2.使用错误处理open(file, r, encodingutf-8, errorsignore)会忽略无法解码的字符但可能导致数据丢失。3.使用chardet库自动检测适用于复杂情况。4.用二进制模式读取open(file, rb)读取字节再手动解码。写入文件后内容丢失或乱码1.模式用错想追加却用了w模式直接清空了原文件。2.编码不一致写入时用了encodingutf-8但用其他编码如系统默认的编辑器打开查看。3.忘记关闭文件或flush()在with语句外数据可能还在缓冲区未写入磁盘。1.确认打开模式追加用a新建/覆盖用w。2.统一编码确保读写使用相同的编码推荐始终使用utf-8。3.坚持使用with语句它能确保文件关闭和缓冲区刷新。读取大文件时程序卡死或内存溢出使用了f.read()或f.readlines()一次性将整个文件加载到内存。如果文件超过可用内存就会出问题。使用迭代或分块读取1.for line in f:逐行处理。2. 使用f.read(size)指定每次读取的字节数循环读取。CSV文件在Excel中打开中文乱码Excel在打开UTF-8编码的CSV文件时可能无法自动识别编码尤其是无BOM的UTF-8。写入时添加BOM字节顺序标记with open(file.csv, w, encodingutf-8-sig, newline)。utf-8-sig编码会在文件开头写入BOM帮助Excel识别。PermissionError: [Errno 13] Permission denied1. 没有文件读取权限。2. 文件正在被其他程序如文本编辑器、另一个Python进程独占打开尤其是在Windows上。3. 尝试写入一个只读文件或受保护的系统目录。1.检查文件权限。2.关闭可能占用该文件的所有其他程序。3.以管理员身份运行程序不推荐作为常规做法。4.选择有写入权限的目录存放输出文件。使用print输出到文件内容不对齐或有额外空格print函数默认会在输出末尾添加换行符并且多个参数间会用空格分隔。直接重定向到文件可能不符合预期格式。使用文件对象的write()方法进行精确写入f.write(f{name}: {score}\n)。或者控制print的参数print(name, score, sep,, end, filef)。独家排查技巧当你遇到一个诡异的文件操作错误尤其是权限或路径问题时可以尝试以下“三步法”最小化复现写一个最简单的脚本只包含出错的open语句看错误是否依然存在。这能排除你主程序中其他代码的干扰。打印完整路径在open之前用os.path.abspath()或Path.resolve()打印出你试图打开的绝对路径确认它和你想象的是否一致。手动操作验证在终端或文件管理器中尝试用相同的路径进行catLinux/Mac或typeWindows命令或者直接双击打开看系统是否能正常访问。这能帮你判断问题是出在Python代码还是系统环境上。6. 从练习到实战构建一个简单的日志分析脚本掌握了100题中的大部分知识点后我们就可以尝试把它们组合起来解决一个真实的微需求。假设你是一个运维人员需要每天检查Nginx的访问日志找出访问量最高的前5个IP地址。这是一个典型的文件读取、数据提取、统计排序和输出的任务。需求分析access.log文件统计每个IP地址的出现次数并输出访问量前5的IP和次数。实现步骤读取文件使用with语句和utf-8编码打开日志文件。提取IP假设日志格式为常见的组合日志格式IP地址是每行的第一个字段以空格分隔。我们需要从每一行中提取出IP。统计计数使用字典dict来存储IP和其出现次数的映射关系。排序输出将字典按值次数降序排序取出前5项。处理异常考虑文件不存在、格式异常等情况。代码实现from pathlib import Path from collections import defaultdict # 使用defaultdict简化计数 def analyze_top_ips(log_file_path, top_n5): 分析日志文件找出访问量最高的IP地址。 Args: log_file_path (str): 日志文件路径。 top_n (int): 需要输出的前N个IP默认为5。 log_path Path(log_file_path) # 1. 检查文件是否存在 if not log_path.is_file(): print(f错误日志文件 {log_file_path} 不存在。) return ip_counter defaultdict(int) # 默认值为0的计数器 try: # 2. 读取并分析文件 with open(log_path, r, encodingutf-8) as f: for line_num, line in enumerate(f, start1): line line.strip() if not line: # 跳过空行 continue # 3. 提取IP简单假设IP是第一个以空格分隔的字段 parts line.split() if not parts: print(f警告第{line_num}行为空或格式异常已跳过。) continue ip parts[0] # 简单的IP格式验证IPv4 if . in ip and ip.count(.) 3: ip_counter[ip] 1 else: # 可能不是IP或者是IPv6等这里简单记录或跳过 # print(f警告第{line_num}行首字段 {ip} 不符合常见IP格式已计入统计。) ip_counter[ip] 1 # 仍计入但可能不是有效IP except UnicodeDecodeError: print(错误文件编码可能不是UTF-8请尝试其他编码如gbk。) return except Exception as e: print(f读取文件时发生未知错误{e}) return # 4. 检查是否有数据 if not ip_counter: print(日志文件中未找到有效数据。) return # 5. 排序并获取前N名 # 将字典项转为(ip, count)的列表然后按count降序排序 sorted_ips sorted(ip_counter.items(), keylambda item: item[1], reverseTrue) top_ips sorted_ips[:top_n] # 6. 输出结果 print(f\n 访问日志分析报告 ) print(f分析文件{log_path.resolve()}) print(f总独立IP数{len(ip_counter)}) print(f\n访问量前 {top_n} 的IP地址) print(- * 40) for rank, (ip, count) in enumerate(top_ips, start1): print(f{rank:2d}. {ip:20s} : {count:6d} 次) # 7. 可选将结果写入文件 output_file log_path.parent / f{log_path.stem}_top_ips.txt try: with open(output_file, w, encodingutf-8) as out_f: out_f.write(fTop {top_n} IPs from {log_path.name}\n) out_f.write(*30 \n) for ip, count in top_ips: out_f.write(f{ip}: {count}\n) print(f\n详细结果已保存至{output_file}) except IOError as e: print(f\n警告无法将结果写入文件 {output_file}错误{e}) # 使用示例 if __name__ __main__: analyze_top_ips(./logs/access.log, top_n5)脚本解析与进阶思考使用defaultdictdefaultdict(int)使得我们不需要在字典中检查IP是否存在直接ip_counter[ip] 1即可如果IP不存在会自动初始化为0。逐行处理与枚举enumerate(f, start1)在遍历文件行的同时提供行号这在打印警告或错误信息时非常有用。简单的数据清洗使用line.strip()去除首尾空白并跳过空行。对IP格式做了最基础的验证包含3个点实际生产环境可能需要更严格的正则表达式匹配。排序技巧sorted(ip_counter.items(), keylambda item: item[1], reverseTrue)是Python中按字典值排序的经典写法。items()返回键值对元组keylambda item: item[1]指定按元组的第二个元素即次数排序reverseTrue表示降序。输出格式化{rank:2d}表示将rank格式化为宽度为2的十进制整数右对齐{ip:20s}表示将ip格式化为宽度20的字符串左对齐。这让输出看起来更整齐。路径操作log_path.parent获取日志文件所在目录log_path.stem获取不带扩展名的文件名用它们来构造输出文件路径非常方便。这个脚本虽然只有百来行但它综合运用了文件读取、字符串处理、数据结构字典、排序、格式化输出、异常处理等多个知识点。你可以在此基础上继续扩展比如分析不同HTTP状态码的数量、统计最频繁访问的URL、按小时统计访问量等。每一次扩展都是对你文件处理能力的又一次锤炼。
返回列表