ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

前端转 AI 100 天 Day 5:文件读写与异常处理,程序的“记忆“和“保险“

前端转 AI 100 天 Day 5:文件读写与异常处理,程序的“记忆“和“保险“ 前端转 AI 第 5 天文件读写与异常处理程序的记忆和保险昨日回顾Day 4 把面向对象搞定了self、继承、魔法方法、property都过了一遍还写了ChatMessage类。今天进入两个看起来很基础、但真正做项目时天天要用的东西文件读写和异常处理。前端同学可能觉得这两个很陌生——我们平时读写文件靠fs异常靠try/catch都是 Node 那一套。今天要把 Python 的思路建立起来。Day 5 目标能读写文件、能处理异常、能操作路径。一、为什么这两块特别重要先说清楚为什么要花一整篇讲这个文件Agent 要保存对话历史、要读取知识库文档、要写日志异常调 API 会超时、JSON 会解析失败、模型会返回垃圾格式程序不能一崩了之前端日常跑在浏览器里崩了刷新一下就行。但 Agent 是常驻进程跑在服务器上一崩就是事故。所以这两块不是基础语法是写健壮程序的必备技能。二、文件读写基础1. 最简单的读文件# 打开文件fopen(demo.txt,r,encodingutf-8)# 读取内容contentf.read()# 关闭文件f.close()print(content)但这样写有个坑如果f.read()抛异常了f.close()就不会执行文件句柄泄漏。2. 正确写法with语句withopen(demo.txt,r,encodingutf-8)asf:contentf.read()print(content)with会自动关闭文件不管中间有没有抛异常。这是 Python 的标配写法记住它。JS 类比// Node 里其实没有直接对应最接近的是constcontentfs.readFileSync(demo.txt,utf-8);Node 的readFileSync一行搞定Python 要with三行——看起来更啰嗦但 Python 的写法支持流式读取大文件JS 那行会把整个文件塞内存。⚠️ 坑点encodingutf-8千万别省Windows 上默认是 GBK读中文文件会乱码。这个坑一次都别踩每次都写。3. 文件模式模式含义文件不存在时文件存在时r只读报错从头读w只写新建清空重写a追加新建末尾追加r读写报错从头读写rb/wb二进制同上同上最容易踩的坑w会清空文件withopen(log.txt,w,encodingutf-8)asf:f.write(第一行\n)withopen(log.txt,w,encodingutf-8)asf:f.write(第二行\n)# log.txt 现在只有第二行第一行被清掉了想追加就用awithopen(log.txt,a,encodingutf-8)asf:f.write(新的一行\n)4. 三种读法# 1. 全部读成字符串withopen(demo.txt,r,encodingutf-8)asf:textf.read()# 2. 全部读成列表每行一个元素withopen(demo.txt,r,encodingutf-8)asf:linesf.readlines()# [第一行\n, 第二行\n]# 3. 一行一行读大文件用这种withopen(big_file.txt,r,encodingutf-8)asf:forlineinf:print(line.strip())# strip 去掉末尾换行大文件一定用第三种。read()会把整个文件塞进内存几个 G 的日志直接 OOM。5. 写文件# 写入一行withopen(output.txt,w,encodingutf-8)asf:f.write(Hello\n)f.write(World\n)# 写入多行lines[第一行,第二行,第三行]withopen(output.txt,w,encodingutf-8)asf:f.writelines(line\nforlineinlines)注意writelines不会自动加换行符需要自己加\n。三、路径操作用pathlib别用字符串拼接前端的习惯是path.join()拼路径。Python 里也有os.path.join但更推荐用pathlib因为它更直观。frompathlibimportPath# 当前目录print(Path.cwd())# 拼接路径用 / 运算符超直观data_dirPath(data)file_pathdata_dir/users/list.jsonprint(file_path)# data/users/list.json# 判断print(file_path.exists())# 文件/目录是否存在print(file_path.is_file())# 是文件吗print(file_path.is_dir())# 是目录吗# 创建目录exist_okTrue 表示已存在也不报错data_dir.mkdir(exist_okTrue,parentsTrue)# 读取/写入pathlib 自带的快捷方法file_path.write_text(Hello,encodingutf-8)contentfile_path.read_text(encodingutf-8)# 遍历目录forpindata_dir.iterdir():print(p)这套 API 比os.path好用太多特别是Path(a) / b / c这个/运算符第一次看到真的惊艳。JS 类比path.join(a,b,c);Path(a)/b/c# 更优雅常用属性pPath(data/users/list.json)p.name# list.json 文件名p.stem# list 不带后缀的名字p.suffix# .json 后缀p.parent# Path(data/users) 父目录p.parts# (data, users, list.json)四、异常处理别让程序一崩了之1. 基础语法JStry{JSON.parse(不是JSON);}catch(e){console.error(e.message);}finally{console.log(always);}Pythontry:dataint(abc)exceptValueErrorase:print(f转换失败:{e})finally:print(always)几乎一样关键字对应JavaScriptPythontrytrycatchexceptfinallyfinallythrowraise2. 捕获具体异常别裸奔except# ❌ 反面教材try:do_something()except:# 捕获所有异常包括 CtrlCpass这样写会把所有错误都吃掉包括你打错变量名的NameError出了问题根本查不到。正确姿势try:dataint(user_input)exceptValueErrorase:print(f输入的不是数字:{e})exceptKeyboardInterrupt:print(用户中断)raise# 重新抛出3. 一次捕获多个try:resultdo_something()except(ValueError,TypeError)ase:print(f出错:{e})4. 完整结构try:# 可能出错的代码dataint(123)exceptValueErrorase:# 出错时执行print(fValueError:{e})exceptExceptionase:# 其他错误兜底print(f未知错误:{e})else:# 没出错时执行比放在 try 里更清晰print(f结果:{data})finally:# 无论如何都执行清理资源print(清理完成)else是很多人不知道的——try里只放可能出错的代码成功了再在else里做后续处理层次更清晰。5. 常见内置异常异常触发场景JS 类比ValueError值不合法int(abc)ErrorTypeError类型错误1 aTypeErrorKeyError字典 key 不存在undefined报错IndexError列表下标越界undefinedFileNotFoundError文件不存在ENOENTAttributeError属性不存在undefined.xImportError模块导入失败MODULE_NOT_FOUNDTimeoutError超时ETIMEDOUT做 Agent 时最常遇到的两个TimeoutErrorAPI 超时和ValueError模型返回的 JSON 解析失败。今天先有印象。6. 主动抛异常raisedefdivide(a,b):ifb0:raiseValueError(除数不能为 0)returna/btry:divide(1,0)exceptValueErrorase:print(e)# 除数不能为 0JS 类比throw new Error(...)Python 就是raise ValueError(...)。7. 自定义异常classLLMError(Exception):大模型调用失败passclassLLMTimeoutError(LLMError):大模型超时passdefcall_llm(prompt):raiseLLMTimeoutError(请求超时请重试)try:call_llm(你好)exceptLLMTimeoutErrorase:print(f超时了:{e})exceptLLMErrorase:print(fLLM 错误:{e})关键点自定义异常继承Exception然后可以分组捕获。这个模式在做 Agent 时会大量使用——每种失败对应一种异常然后按类型重试或降级。五、实战练习日志记录器把今天学的东西串起来写一个日志工具。新建day05/logger.pyfrompathlibimportPathfromdatetimeimportdatetimeclassLogger:一个简单的文件日志记录器LEVELS{DEBUG:10,INFO:20,WARN:30,ERROR:40}def__init__(self,log_path:str,level:strINFO):iflevelnotinself.LEVELS:raiseValueError(f不支持的日志级别:{level})self.log_pathPath(log_path)self.levelself.LEVELS[level]# 确保父目录存在self.log_path.parent.mkdir(exist_okTrue,parentsTrue)def_write(self,level:str,message:str):ifself.LEVELS[level]self.level:return# 低于当前级别不写timestampdatetime.now().strftime(%Y-%m-%d %H:%M:%S)linef[{timestamp}] [{level}]{message}\n# 用 a 模式追加不清空try:withopen(self.log_path,a,encodingutf-8)asf:f.write(line)exceptOSErrorase:# 写日志本身失败了不能让它把主程序带崩print(f写日志失败:{e},flushTrue)defdebug(self,msg):self._write(DEBUG,msg)definfo(self,msg):self._write(INFO,msg)defwarn(self,msg):self._write(WARN,msg)deferror(self,msg):self._write(ERROR,msg)defread_all(self):读取全部日志ifnotself.log_path.exists():return[]returnself.log_path.read_text(encodingutf-8).splitlines()# 模拟调用 LLM 的函数 deffake_call_llm(prompt):模拟调用大模型演示异常处理ifnotprompt:raiseValueError(prompt 不能为空)if超时inprompt:raiseTimeoutError(请求超时)returnf回答:{prompt}的答案# 测试 if__name____main__:loggerLogger(logs/app.log,levelINFO)logger.debug(这条不会写因为级别低于 INFO)logger.info(程序启动)test_prompts[介绍一下 Python,,超时测试,什么是 Agent]forpintest_prompts:try:logger.info(f调用 LLM:{p!r})resultfake_call_llm(p)logger.info(f调用成功:{result})exceptValueErrorase:logger.warn(f参数错误:{e})exceptTimeoutErrorase:logger.error(f调用超时:{e})exceptExceptionase:logger.error(f未知错误:{type(e).__name__}-{e})print( 日志内容 )forlineinlogger.read_all():print(line)运行结果 日志内容 [2026-09-14 14:30:01] [INFO] 程序启动 [2026-09-14 14:30:01] [INFO] 调用 LLM: 介绍一下 Python [2026-09-14 14:30:01] [INFO] 调用成功: 回答: 介绍一下 Python 的答案 [2026-09-14 14:30:01] [INFO] 调用 LLM: [2026-09-14 14:30:01] [WARN] 参数错误: prompt 不能为空 [2026-09-14 14:30:01] [INFO] 调用 LLM: 超时测试 [2026-09-14 14:30:01] [ERROR] 调用超时: 请求超时 [2026-09-14 14:30:01] [INFO] 调用 LLM: 什么是 Agent [2026-09-14 14:30:01] [INFO] 调用成功: 回答: 什么是 Agent 的答案这个小项目用到了今天所有知识点pathlibPath拼接、mkdir、read_text文件读写a追加模式、with自动关闭、encodingutf-8异常处理分类捕获ValueError/TimeoutError/Exception自定义异常虽然这里用的是内置的但模式一样面向对象Day 4 的复习重点logger._write里用了try/except包住写文件的操作。这是生产代码的常见模式——不要让日志失败带崩主流程。六、今日踩坑忘写encodingutf-8→ Windows 上读中文乱码w模式清空文件→ 想追加必须用a裸except:→ 会吞掉所有错误包括你想看到的with里拿f.read()后忘了用→ 出with块后f已关闭不能再操作Path.mkdir()不加parentsTrue→ 多级目录会失败七、今日总结今天核心记住三件事with open(...) as f:是标准写法encodingutf-8是标配Path(a) / b比字符串拼接路径优雅一百倍异常要分类捕获用except ValueError不要裸except:一个感受前端的异常处理是别让页面白屏Python 的异常处理是别让进程挂掉。后者要求更严谨因为没人帮你按刷新。今天写的Logger类后面做 Agent 的时候会直接拿来改一改就用。这不是练习是预演。Day 5 完成度✅明日预告Day 6pip / conda 到底怎么选虚拟环境原理为什么要有venv/condarequirements.txt/pyproject.toml小练习从零搭一个项目结构装第一个第三方库最后如果这篇对你有用点个赞让我知道有人在看。有问题评论区见我们第 6 天继续。100 天第 6 天见。附系列目录持续更新天标题01从切图仔出发100 天走向 Agent 开发 ✅02list / dict / 推导式Python 比 JS 爽在哪 ✅03函数、参数、作用域别再写function了 ✅04面向对象class 和 JS class 的异同 ✅05文件读写与异常处理 ✅06pip / conda / 虚拟环境彻底搞懂依赖管理07requests 爬虫实战抓一个 API08异步 asyncioPromise 的另一种形态……31FastAPI 入门写第一个接口46调通第一个大模型 API61RAG 实战让模型读懂你的文档81LangChain Agent让模型自己调工具100上线我的第一个 AI Agent 应用
返回列表