文件读写核心模式解析:r/w/a在游戏测试中的应用
做游戏测试这行天天跟配置文件、日志文件、测试报告打交道。游戏客户端启动之前要读一堆配置自动化用例跑完要写报告线上出了问题要从海量日志里捞关键报错——这些活儿有一个共同点全落在“文件读写”这四个字上。我带的 8 周通关 Python 训练营第二周正好走完变量、数据类型、流程控制、函数这些基础语法收尾的重头戏就是文件读写。今天这篇把第二周的知识点整合起来完整过一遍重点拆解文件读写的三大核心模式也就是大家常说的 r、w、a顺便把配套的 with 语句、编码处理、路径坑一次性讲透。如果你是刚装好 Python、正准备往游戏测试方向走的同学这篇可以直接当第二周复习笔记用。如果你已经会写几行脚本但文件操作总在编码和路径上翻车那第四、五两节的内容应该能帮你少踩几个坑。1. 第二周知识地图文件操作之前你还需要会什么训练营第二周安排得比较紧凑从基础语法一路推到文件操作。很多同学急着上手写文件读写却忽略了前置知识结果代码一跑就是 NameError 或者 TypeError。这里先花几分钟把知识地图铺开。1.1 变量、类型与流程控制是地基第二周前半段讲的变量赋值、字符串、列表、字典、if/else、for/while这些在文件操作里都会用到。比如读取一个文本文件并统计关键字的出现次数你至少要会用 with 打开文件、用 for 循环逐行遍历、用字典或者计数器变量累积数据。如果流程控制还不熟建议先回头把 for 循环和 if 判断练顺否则后面读日志写报告的代码会看得一头雾水。我见过不少同学直接跳过基础去抄文件读写代码抄的时候能跑一换文件路径就懵了。原因就是没理解变量和类型转换。文件读出来的内容默认是字符串如果你需要去比较数字大小必须先 int() 转换如果你要把多段内容拼起来写进文件字符串拼接和 f-string 要熟练。这些知识点不是孤立存在的文件操作会把它们全部串起来。1.2 函数封装让文件操作代码真正可用第二周后段开始讲函数这块和文件操作的关系非常紧密。你要是写一个读取配置文件的逻辑只写一遍还行但游戏测试里往往要在多个用例里重复读同一份配置。把读文件、解析、返回结果封装成一个函数后续每个用例只要调用函数一行代码就搞定比到处复制粘贴代码强太多。封装函数还有一个好处错误处理能集中管理。比如文件不存在、路径写错、编码异常这些都可以在函数体内用 try/except 拦下来返回一个默认值或者明确的报错信息。这样主流程代码干净清爽排查问题也快。第二周布置的小作业基本都会要求你写一个“读取配置文件并打印关键项”的函数本质上考的就是函数加文件读写的组合能力。1.3 环境与工具跑得起来才有后面的事再提一嘴环境。如果你还没装 Python或者 VSCode 里跑脚本总是报找不到解释器先把环境弄好再往下看。Windows 装 Python 记得勾选“Add Python to PATH”装完在命令行敲 python --version 能出版本号就算成功VSCode 里装好 Python 扩展CtrlShiftP 选一下解释器就能跑。这一步不复杂但卡住的人真不少而且大部分问题都出在 PATH 没配上。环境顺了后面的代码练习才有意义不然你连报错是来自代码还是来自环境都分不清。2. 文件读写三大核心模式r、w、a 的底层逻辑Python 内置的 open() 函数是文件操作入口它的第二个参数叫“模式”也就是你打开文件打算干什么。三大核心模式就是指读模式 r、写模式 w、追加模式 a。理解这三者的区别比背十个模式参数更重要因为后面所有的读写逻辑都是围绕“你打开文件时要做什么”来设计的。2.1 读模式 r最常用的打开方式r 代表 read以只读方式打开文件。文件必须已经存在否则直接抛 FileNotFoundError。它是 open() 的默认模式也就是说 open(config.ini) 和 open(config.ini, r) 是等价的。读模式适合所有“只需要查看内容、不能修改原文件”的场景。比如读取游戏存档文件、读取关卡配置、读取日志文件做分析。r 模式打开文件后常见的读取方法有三个read() 一次性读出全部内容返回字符串readline() 每次读一行readlines() 读出所有行返回列表。选哪个取决于文件大小和处理需求后面第三节会结合例子讲。这里有个细节值得留意r 模式下指针默认在文件开头如果你先用 read() 读完了整个文件再想用 readline() 读第一行读到的会是空字符串因为指针已经跑到末尾了。想回到开头可以用 seek(0)这个操作在分析大文件时非常实用。2.2 写模式 w能创建文件也会清空文件w 代表 write以写入方式打开文件。和 r 最大的区别是如果文件不存在w 模式会直接创建一个新文件如果文件已存在w 模式会把文件内容全部清空再写入。这个“清空”行为是新手最容易踩的坑——你本来想更新某一行配置结果一运行代码整个文件内容没了。我自己就干过这事。有一次写测试数据生成脚本想往一个已有的 CSV 里补几行数据随手用了 w 模式跑完发现原文件里的历史数据全没了。所以记住一句口诀w 是“重新开始”a 是“接着写”。如果你需要保留原有内容千万别用 w。w 模式还承担一个职责创建新文件。比如测试跑完要生成一份当天的报告文件用 w 模式打开一个不存在的路径Python 会自动建出来省去手动建文件的步骤。但要注意如果路径中的目录不存在w 模式不会自动创建目录会抛 FileNotFoundError这点很多人没意识到。2.3 追加模式 a日志和记录类场景的主力a 代表 append以追加方式打开文件。文件不存在会创建新文件文件已存在则不清空写入的内容会追加到文件末尾。a 模式最典型的应用场景就是写日志每次运行脚本或者每次测试用例执行都把记录追加到同一个日志文件里保留完整的历史轨迹。需要注意 a 模式下指针的位置。追加模式打开文件后不管你之前有没有调用 seek() 移动指针写入操作都会把内容写到文件末尾。这一点和 r、w 不同后两者写入位置取决于当前指针。如果你只是写日志、写增量记录用 a 模式最省心不用管指针。三大模式选型其实想清楚一个问题就行你打开这个文件是要“看”还是“写”写的时候是“从零开始”还是“接着续写”。看内容用 r从零开始写用 w接着续写用 a。就这么简单。2.4 with 语句更省心的打开关闭方式说完三大模式必须带上好搭档 with。open() 打开文件后用完了要 close() 关闭否则文件句柄一直被占用Windows 下甚至会导致后续进程无法读写同一个文件。但很多人写着写着就忘掉 close()或者代码中途抛异常close() 没执行。with 语句会在代码块结束后自动帮你关闭文件即使中途出异常也会执行清理。推荐写法是这样的with open(test_report.txt, w, encodingutf-8) as f: f.write(测试结果)代码块结束时文件自动关闭你完全不需要手动调用 f.close()。这个写法除了省事还避免了一个经典坑写入内容还在缓冲区里没落盘程序就异常退出导致文件内容丢失。with 正常结束时Python 会保证缓冲内容刷到磁盘。养成用 with 的习惯文件操作的问题能少一半。3. 实操走一遍游戏测试里的三个真实场景光讲概念记不住直接上场景。下面三个例子我都是从实际测试工作里抽出来的代码都不长但覆盖了 r、w、a 三大模式和常见细节。建议你本地跑一遍把路径换成自己机器上的实际文件路径。3.1 场景一读取游戏配置文件并解析关键项大部分游戏客户端启动时会读取一个配置文件里面可能有服务器地址、端口号、画面质量等参数。测试环境切换时我们需要快速确认当前配置是不是对的。假设配置文件 log_config.ini 内容如下[server] host192.168.1.100 port8080 [client] languagezh-CN resolution1920x1080读取这个文件并打印关键配置项代码可以这么写with open(log_config.ini, r, encodingutf-8) as f: for line in f: line line.strip() if line.startswith(host): host line.split()[1] print(服务器地址:, host) elif line.startswith(port): port int(line.split()[1]) print(端口号:, port)这里用 r 模式配合 for 循环逐行读取。为什么要逐行读而不是 read() 一次性读出来因为配置文件一般不大两种方式都行但逐行读的思路可以无缝迁移到大日志文件的处理上养成分行处理的习惯不吃亏。这段代码里有两个细节。第一line.strip() 去掉行尾的换行符和行首的空白不然打印出来的内容会带空行。第二port 用 int() 转换成整数因为后面如果要做端口连通性判断拿字符串比较会出问题。这就是第二周知识点里“类型转换”在文件操作里的落地。读取更规范的配置文件可以直接用 configparser 模块但手写解析一次能帮你把字符串方法练熟。训练营作业阶段我更推荐先手写理解每一行是怎么被处理的后面再用现成模块会更快。3.2 场景二生成自动化测试报告游戏自动化测试跑完通常要输出一份报告。最朴素的形式就是写一个文本报告记录每个用例的通过情况、失败原因、耗时。这里用 w 模式最合适因为每次跑完测试我们只需要当次的结果旧报告可以直接覆盖。results [ (login_test, PASS, 1.2), (battle_test, FAIL, 3.5), (shop_test, PASS, 0.8), ] with open(report.txt, w, encodingutf-8) as f: f.write(游戏自动化测试报告\n) f.write( * 40 \n) for case_name, status, duration in results: f.write(f{case_name}: {status}, 耗时 {duration}s\n) f.write( * 40 \n) pass_count sum(1 for item in results if item[1] PASS) f.write(f通过用例数: {pass_count}/{len(results)}\n)运行之后report.txt 里应该是清晰的文本报告。这里用 w 模式是合理的因为每次生成的都是全新报告旧内容没有保留价值。f.write() 不会自动加换行所以每行结尾要自己加 \n。这个细节在刚上手时经常被忽略结果所有内容挤成一行看起来像“乱码”。如果你想写结构化报告后续可以换成 CSV 格式。CSV 本质也是文本文件用 w 模式打开每行用逗号分隔字段Excel 里直接双击打开就能看测试组同事用起来非常方便。这个方法在第二周就可以练起来后面进阶到 pytest 生成 HTML 报告之前用文本和 CSV 报告已经能覆盖大部分工作场景。3.3 场景三增量记录测试操作日志测试执行期间我们希望把每一步关键操作追加到同一个日志文件里方便事后回溯。这时就是 a 模式的天下。def log_step(msg): with open(test_log.txt, a, encodingutf-8) as f: f.write(f[{time.time():.0f}] {msg}\n) log_step(开始执行登录测试) log_step(输入用户名密码) log_step(点击登录按钮等待响应)每次调用 log_step都会往 test_log.txt 末尾追加一行不会覆盖之前的记录。这个模式非常适合长时间运行的测试脚本跑一晚上日志文件里就是完整的时间线。配合 time 模块记录时间戳后续分析问题能精确到秒。需要提醒的是频繁打开关闭文件会有性能开销但日志写入这种低频操作完全没问题。如果日志量极大每秒几十条甚至上百条那就应该在内存里攒一批再批量写入或者直接用 logging 模块。训练营阶段用 open a 模式理解追加逻辑就够了等进入项目阶段再考虑性能优化。4. 编码、路径和异常新手踩坑实录文件读写报错大概有一半集中在编码和路径上剩下的一半是忘记关闭文件、忘记处理异常。这些坑我在训练营里几乎每一期都能见到这里直接整理成一份排查清单省得你一个个试。4.1 编码问题utf-8 与 gbk 之争Windows 下用 open() 读文本文件默认编码不是你想象的 utf-8而是跟随系统区域设置中文 Windows 默认可能是 gbk。如果你用记事本新建的 txt 文件保存编码是 ANSI在中文系统下就是 gbk而 Python 按 utf-8 去读就会遇到 UnicodeDecodeError报错信息会提示某个位置的字节无法解码。解决办法很直接打开文件时显式指定编码。with open(config.txt, r, encodingutf-8) as f: content f.read()但事情还没完。如果文件本身是 gbk 编码你指定 utf-8 还是会报错。正确的姿势是先确认文件的真实编码。一个简单的判断方法是用带编码提示的编辑器打开文件看看状态栏或者用 chardet 模块自动检测。在游戏测试环境里很多老旧工具生成的文件仍是 gbk而新脚本默认 utf-8两种编码的文件混在一起很常见。这里有一个我实际项目里的处理经验统一规范比逐个适配更重要。我们后来把所有测试工具输出的文件全部改成 utf-8 编码脚本里也全部显式传 encodingutf-8从根源上消灭了编码混乱。如果你的项目没法统一编码那就写一个探测函数用 chardet 判断编码后再打开文件但这是补救方案不是首选。4.2 路径问题相对路径 vs 绝对路径读写文件时报 FileNotFoundError十有八九是路径写错了。最简单的可能是你写的文件名不在当前工作目录下。很多人误以为 py 文件在哪个目录文件就在哪个目录其实 Python 找相对路径是相对于“当前工作目录”也就是你在命令行里执行命令时所在的位置不一定是脚本目录。两个稳妥的办法。第一直接用绝对路径路径写错至少能一眼看出来。第二用 pathlib 模块基于当前文件的位置动态拼接路径from pathlib import Path base_dir Path(__file__).parent file_path base_dir / data / config.ini with open(file_path, r, encodingutf-8) as f: content f.read()Path(file).parent 拿到的是当前脚本所在目录再用 / 拼接子目录和文件名不管你在哪个目录下执行这个脚本路径都不会出错。这个方法在自动化测试框架里尤其重要因为测试脚本可能被 CI 系统从不同目录调用硬编码相对路径必炸。另外要提醒一点w 模式和 a 模式打开不存在的文件会自动创建但前提是文件的父目录必须存在。如果 data 目录不存在open() 不会帮你创建它照样抛 FileNotFoundError。需要先建目录可以用 Path.mkdir(parentsTrue, exist_okTrue) 一行解决。4.3 文件不存在、权限不足怎么处理读一个不存在的文件Python 抛 FileNotFoundError对只读文件执行写操作抛 PermissionError把目录当文件打开抛 IsADirectoryError。这些是文件操作的常见异常不该让它们直接中断主程序。标准做法是 try/except 包一层。try: with open(test_report.txt, r, encodingutf-8) as f: content f.read() except FileNotFoundError: print(报告文件不存在请先执行测试生成报告) except PermissionError: print(没有权限读取该文件检查文件属性)在游戏测试脚本里稳定运行比功能丰富更优先。一个用例跑一半因为日志文件被占用而崩溃整批测试都白跑。所以凡是文件操作我习惯都套上异常处理至少保证即使读取失败主流程也能继续或者给出明确提示。异常处理如果只写一个裸 except:后续排查问题会非常痛苦因为你看不到任何有效信息还是老老实实区分异常类型比较好。4.4 文件没关、内容没刷新的坑不用 with 的写法很多人会忘记 f.close()。不关闭文件最直接的问题是句柄占用Windows 下删除文件时报“文件正在被另一进程使用”的提示十有八九是某个脚本还开着这个文件没关。另一个隐蔽问题是缓冲区。f.write() 的内容先是进内存缓冲区close() 或者程序正常退出时才会真正落盘。如果你写了内容但没有关闭文件也没有 flush()程序一旦异常退出内容可能就丢了。这也是我前面强烈建议用 with 的原因——它不仅帮你 close还能保证正常退出时内容完整落盘。某次跑批量脚本数据写了一半程序崩了文件里只有半截内容排查了半天才意识到是没等缓冲落盘就强杀进程。从那以后我写文件只用 with。5. 进阶细节文档里不会明说的几个经验最后再补几个文件操作里容易被忽略、但实际用得上或者说迟早会用上的细节点。它们不属于第二周必考范围但提前知道能让你后面写脚本顺畅不少。5.1 二进制模式 rb/wb图片、压缩包和打包资源游戏测试里除了读写文本还会碰到二进制文件。比如校验资源包的完整性、对比两张截图是否一致、读取加密的存档文件。二进制文件不能用文本模式处理文本模式下 read() 会尝试按编码解码遇到非文本字节就报错。二进制模式就是在模式字符串后面加 brb 读取二进制、wb 写入二进制、ab 追加二进制。读取时返回的是 bytes 类型不是 str。一个典型需求是拷贝文件这时直接二进制读写最稳妥with open(screenshot.png, rb) as src: data src.read() with open(screenshot_copy.png, wb) as dst: dst.write(data)这段代码对任何类型的文件都适用因为全程不涉及编码只搬运字节。游戏资源包、截图、录制视频都可以用这种写法做备份或者分发。二进制的处理思路和文本完全一致只是数据形态从 str 变成了 bytes三大模式的选择逻辑还是一样的。5.2 read、readline、readlines 到底怎么取舍很多人分不清三种读取方法的适用场景。简单说read() 适合小文件一次性读入内存处理最简单readline() 适合逐行处理且每行是独立记录的情况readlines() 适合需要把所有行一次性装进列表、后续反复访问行内容的场景。实际游戏测试里日志文件动不动就是几十 MB这时候千万别用 read() 或者 readlines() 一次性读完内存扛不住。正确姿势是 for line in f 逐行遍历处理完一行丢一行。这里有一个隐藏知识点open() 返回的文件对象本身是可迭代的for line in f 底层就是循环调用 readline()但写起来比手动 readline() 简洁得多也不会出现 readline() 返回空字符串时忘记退出的死循环问题。判断用哪种方式先问自己这个文件最大可能多大处理逻辑是否需要随机访问任意一行文件小随便文件大务必逐行需要随机访问readlines() 装进列表更方便。这个取舍在第三周的日志分析专题里还会反复出现。5.3 模式字符串再加一个加号r、w、a 什么时候用除了 r、w、aopen() 还支持 r、w、a 三种读写混合模式。w 和 a 都能读写但 w 会清空文件a 指针初始在末尾。r 在文件存在时读写指针在开头不会清空内容。混合模式看起来功能更全但我不建议新手一开始就用。原因很简单可读性差容易出隐蔽问题。比如 w 模式打开一个已有文件你明明只想读一下旧内容再写新内容结果一打开旧内容就被清了连后悔的机会都没有。r 模式下读和写的指针位置需要自己注意写之前一不小心就把文件内容覆盖了。在实际测试脚本里绝大多数文件需求都可以靠 r、w、a 的组合解决。真要读改写混杂比如修改配置文件里的某些字段我更推荐的做法是读出来放到内存里在内存中改好再用 w 模式整体写回。这样写出来的逻辑清晰也好调试。混合模式留给确有需要的人第二周阶段先把三大核心模式吃透远比多记几个带加号的模式实在。6. 给正在学第二周内容的你几句掏心窝的话文件读写的核心知识点不算多但每一个细节都能在日常工作里找到对应的坑。我自己带训练营的时候见过太多人把 open() 参数背得滚瓜烂熟一写代码还是各种报错。原因就是练得太少。这个章节的知识点动手敲一遍比看十遍都强。我的建议是今天看完这篇立刻做下面三个练习第一读一个配置文件打印出所有配置项第二写一个程序能把一段文本保存到新文件里再读回来验证内容一致第三连续运行同一个脚本三次每次都往同一个日志文件追加一行记录最后打开日志确认有三条记录。这三个练习对应 r、w、a 三个模式做完基本就掌握了。文件读写是那种“会了觉得简单、不会觉得玄乎”的知识点。它最大的价值不是语法本身而是把前面学过的变量、类型、流程控制、函数全部串起来让你第一次写出“能真实完成任务”的脚本。往后无论是数据分析、自动化测试还是工具开发文件读写都是绕不开的底子。第二周把它啃下来后面的路会顺很多。