ARTICLE DETAIL

资讯详情

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

上机练习第41天:Python命令行记账本项目实战与经验总结

上机练习第41天:Python命令行记账本项目实战与经验总结 说实话写到第41天的时候我反而比第3天更紧张。第3天语法不熟盯着报错挠头就行第41天不一样基础语法、循环、列表字典、函数这些内容都过了一遍也该开始脱离教程独立完成一个“能用的东西”。这一天练完我才算把前面40天平铺直叙的知识点真正缝成了一个整体。“上机练习第41天”可以理解成一种状态一个人连续41天坐在电脑前动手写代码正处在“眼睛会了、手还差点意思”和“手感刚热起来”之间的关键窗口期。这篇文章就是那一天的完整记录包括为什么选择在41天做集成练习、练了什么、每一步怎么拆解、踩了哪些坑以及之后的学习怎么续上。如果你正在自学编程、上培训班的实操课、或者刷完在线课程准备做第一个小项目这篇内容应该能帮上忙。1. 内容整体设计与思路拆解1.1 从第1天到第40天一条可复制的三阶段练习曲线我把前40天分成三个阶段每个阶段的目标完全不一样。第1到10天重点是工具链和基础语法。装Python、配环境变量、选一个顺手到编辑器然后反复练变量、判断、循环、函数。这个阶段每天大概写30到60行代码不需要复杂项目能把语法敲熟就行。第二段是第11到25天开始碰核心数据结构与常用API列表、字典、集合、字符串方法、文件读写、异常捕获、datetime这些内容。这里不再满足于“跑通”而是要能独立写出小脚本比如批量改文件名、生成随机密码、给学生成绩排序。第三段是第26到40天重心转移到函数拆分和模块化把一段又臭又长的脚本拆成有输入有输出的函数。这个节奏不是拍脑袋定的而是踩过坑总结出来的。很多自学的人第一个月特别容易沉迷“看语法”视频一集一集刷代码一行不写结果到了真正要写项目时才发现str.split() 和 str.strip() 都分不清什么时候该用哪个。上机练习的本质就是亲手调用这些API直到形成肌肉记忆。前40天的任务一直比较碎虽然每个功能都能跑但数据和功能之间没有真正串联起来。第41天要做的就是把碎片拼成完整程序。这有点像健身里练腿的日子。单块肌群前面都单独练过但深蹲时臀、腿、核心一起协同发力跟单独做腿屈伸完全是两码事。写代码也一样字典你会用文件读写你会用异常捕获你也会用但让它们在一个程序里配合工作就涉及数据结构设计、函数边界划分、异常触发时机这些问题只有真正组合过才知道哪里会别扭。1.2 为什么偏偏在第41天安排综合项目“第41天”不是一个神奇数字而是长期练习周期里的自然推进点。连续学习大概五到六周新鲜感基本耗尽这是个放弃的高发期。前10天练语法有正反馈学会了新东西很有成就感中间20天练API也还行等到第5周重复训练带来的边际红利开始下降人会开始怀疑“我到底练了有没有用”。对抗这种懈怠最好的办法不是硬撑而是制造一个中等难度的挑战让大脑重新兴奋起来。第41天的综合项目恰好承担了这个角色。第40天结束的时候我已经完成了三个互相独立的小工具但每一个都只覆盖一个知识点。第41天选的项目需要同时用到函数、字典、文件读写、异常处理、测试验证——属于“跳一跳够得着”的难度。完成之后整个人的练习状态会明显不一样因为这个项目带来的成就感足以再撑起下一个阶段而且这个项目之后还能继续迭代天然衔接第42天以后的学习。1.3 当天练习的目标与验收标准开始当天练习之前我先给自己定了几条硬指标比起“今天要写500行”这种空目标实用得多完成一个400行以内的命令行工具不依赖第三方库编译安装后装满标准环境就能直接跑。至少包含一次真实的文件读写而不是把结果只打印在屏幕上。至少拆出5个自定义函数每个函数只做一件事。用pytest给核心函数写至少5个有效用例。全程不看着教程敲代码只依据需求文字和自查清单独立完成。为什么是这5条因为可验收、可量化。我之前看过太多人写“今天学习了文件操作”结果只是在 REPL 里调了一下 open() 和 read()程序一关就算完了。第41天我刻意把检验标准定为“能运行、可测试、能复用”这样练习完的成果可以放进 GitHub以后找工作也能直接拿出来讲。2. 核心细节解析与实操要点2.1 项目选题为什么第一选择是命令行记账本第41天的项目我选了“命令行记账本”。项目选得好不好直接决定练习体验。这个记账本要满足用户能新增一条记录记录里有金额、分类、备注、时间能按分类汇总数据断电后不丢保存在本地文件里。选它的理由有四个。第一贴近生活正反馈强。程序写完我当天就真的拿它记了几笔开销有一种“工具马上能服务自己”的爽感。第二字段清晰只涉及金额、分类、备注、时间不牵扯复杂的接口和外部依赖。第三全面覆盖前面学的核心知识点数据存储需要文件操作分类汇总需要字典和列表遍历交互过程需要input输入和异常处理。第四后续扩展空间大从命令行升级到Web版、加上图表展示路径非常自然。这相当于裁缝做一身简单衬衫既要练直线车缝也要练转弯收边练完就能穿出门于是所有动作都有了明确意义。如果一上来就选聊天机器人、爬虫这类项目热度是高但前置知识缺口太大写两天就卡死了反而不利于坚持。2.2 数据结构与文件格式的选择数据存哪里用JSON文件。很多人第一反应是CSV因为它能用Excel打开看着直观。JSON则是无边界的结构化数据嵌套字段处理起来特别舒服。记账这个场景记录本身就是“列表字典”的组合JSON几乎不用做额外转换。我没选SQLite不是它不好而是第41天这个阶段引入数据库连接、主键约束这些概念会让练习焦点失焦。对于数据我用的关键设置是import json from pathlib import Path DATA_FILE Path(records.json) def load_records(): if not DATA_FILE.exists(): return [] with open(DATA_FILE, r, encodingutf-8) as f: return json.load(f) def save_records(records): with open(DATA_FILE, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2)这里必须重点说 encodingutf-8 和 ensure_asciiFalse 这两个参数。不写 encodingWindows 上很容易遇到 UnicodeDecodeError 或者文件变成一坨 \u 转义因为 Windows 默认的编码是 GBK用了 ensure_asciiFalse中文才会以可读形式存进文件否则全变成 \u6b3e\u9879 这种。indent2 是纯给人类可读性准备的不算功能但开了它之后手动打开 records.json 排查数据时体验好很多。关于数据导入导出我顺手做了一个限制文件以最简方式存储不维护自增ID。这样做的原因是第41天阶段还不需要数据库主键的概念。列表里每条记录靠“索引时间戳”就行了要坚持读懂“数据文件是可读的”这个直觉。2.3 功能边界与函数分级项目虽然小我也强行按三层来组织了。数据层负责读写业务层负责处理交互层负责与用户对话。数据层load_records() 和 save_records()其他代码不直接碰文件。业务层add_record()、delete_record()、summary_by_category()不关心输入是怎么来的。交互层main() 里负责命令行选项和 input() 提示。为什么要这样拆因为不拆最后很容易变成一个大函数里堆着 open、input、print、sum看起来能跑但一个月后自己都看不懂。单一职责原则在这个小项目里就开始练后面写稍大的程序时你就不会不习惯了。拆函数时我遵循一个粗标准每个函数以动词开头参数最多不超过3个。一条记账数据就三个核心字段金额、分类、备注加上时间戳再放一个数据文件路径作为可注入参数刚好卡在4个以内。3. 实操过程与核心环节实现3.1 环境准备与工程初始化操作系统是Windows 11Python用的是3.10因为 3.8 以上都完全够用不需要最新版抛头露面。编辑器选VS Code配好Python扩展就行不用额外装重型IDE。整个项目就一个文件加一个测试文件不搞虚拟环境也没用requirements.txt。我这样操作的原因很简单第41天上机练习的重点是逻辑闭环和工程习惯不是部署发布。先建目录结构lesson41/ ├── records.py # 主程序 ├── test_records.py # 测试文件 ├── records.json # 数据文件运行后自动生成 └── README.md # 记录练习目标与使用说明工程初始化我还做了一件事git init 并完成了第一次提交。第一次提交只放README和空的主程序框架。版本管理这件事越早养成习惯越好。后面每实现一个功能我提交一次这样万一写崩了还能准确回到上个可用版本。3.2 从需求到代码的落地顺序我不建议一上来就写完整代码而是先把需求转成注释框架再逐块填充。开场先在一张纸上列出单子然后照顺序实现。第一步实现数据层。load_records 的逻辑很简单文件不存在时返回空列表存在时open读取。这里有个我反复提醒自己的坑别用“open(...,r)”默认编码一定要显式传 encodingutf-8。save_records 在所有写操作后调用。单独提这两个函数出来是因为后面不管加多少业务功能读写都往这两个函数收口。第二步实现业务层。add_record 的初始版本长这样def add_record(records, amount, category, note): record { amount: float(amount), category: category.strip(), note: note.strip(), date: datetime.now().strftime(%Y-%m-%d %H:%M:%S) } records.append(record) save_records(records) return record初始版故意没有校验金额这是刻意的。先把主链路跑通再补校验比一上来就写满防御性代码更容易一次调试通过。跑通后再补上if amount 0: raise ValueError(金额必须大于0)第三步实现汇总。按分类汇总的本质是遍历所有记录把相同类别的金额累加。用字典来实现特别自然def summary_by_category(records): result {} for r in records: result[r[category]] result.get(r[category], 0) r[amount] return result这句 result.get(r[category], 0) 是核心它表示“如果分类已经在字典里就用已有值否则从0开始加”。不写这个而用 if category in result:代码会多两行而且读起来乱。第四步实现命令行交互用最简单的 while 循环def main(): records load_records() while True: cmd input(请输入命令add/summary/quit) if cmd add: ... elif cmd summary: ... elif cmd quit: break这里我没用 argparse因为4个命令级别的小工具用 argparse 有点杀鸡用牛刀了。但我在main函数里也留了约束数据从 load_records 读入每次修改后通过 save_records 写回不让任何业务函数直接动手改文件。3.3 单元测试与手动验证的配合测试在这一天正式登场。写测试文件 test_records.py用 pytest 框架。每个用例都通过参数传入数据文件路径避免测试之间污染真实文件。最关键的一个用例是验证 add_record 与 summary_by_category 能连起来工作import json from pathlib import Path import pytest from records import add_record, summary_by_category, load_records def test_add_record_and_summary(tmp_path): # 使用临时目录下的文件不触碰真实 records.json data_file tmp_path / test_records.json ...测试里用 tmp_path 并不是在耍酷。如果测试直接用项目目录里的 records.json跑完第一个用例文件里就多了几条数据第二个用例接着读断言结果可能就不准了。测试隔离是一个必须练的工程素养第41天开始养成后面写任何项目都受益。手动验证也要认真做。写完测试后我重新在命令行里跑了一遍完整流程先 add 了两次“餐饮”、一次“交通”再输入 summary看到输出里两个分类的汇总都正确这才算是“上机完成”了。这个手动验证环节不能省它能捕捉到测试没有覆盖到的交互问题比如输入时不小心敲了空格数据是否会被单独存成一条脏记录。4. 常见问题与排查技巧实录4.1 Windows 下文件编码问题第41天练习中最经典的问题就是我前面反复提到的编码问题。在 Windows 上不写 encodingutf-8 直接读 JSON大概率报UnicodeDecodeError: gbk codec cant decode byte 0x98...。排查这个问题时一开始想不通文件明明是程序自己生成的怎么会读不回来后来查日志才反应过来save 时 json.dump 我传了 ensure_asciiFalse写进文件的是中文load 时 open() 默认按 GBK 解码GBK 和 UTF-8 的中文字节流对不上必然报错。解决就一行代码open 时传 encodingutf-8。经验是所有文件读写、网络传输、数据库连接在编码上永远显式声明别依赖环境默认值。默认值在不同系统、不同运行环境中不一样靠默认值早晚踩坑。这属于“主动为错误买单”Bug 明明可以避免。4.2 金额精度与用户输入的坑用 float 存金额能跑但严格说不够严谨。Python 的二进制浮点数对 12.5 这类小数没有精确表示print 出来没问题但多个小数运算后面会产生很长的尾巴。记账工具练到后面一旦做统计汇总数据异常是迟早的事。第41天先不引 Decimal 模块因为基础阶段的重点是掌握流程。但我做了一步保守处理求和之后 round(sum_value, 2)保证输出至少显示正常。等第50天左右引入 Decimal 再做严谨判断读者到时会明白两个选择之间的区别。其实用户输入的坑比精度更隐蔽。有人手滑输入了负数初始版本没有校验程序会默默把一笔“负支出”记进去后面汇总全乱。这个经验强调一个原则任何程序的对外入口默认不信任输入校验放在最靠近入口的位置。4.3 pytest 测试相互污染测试第一次跑全绿第二次跑就挂这个现象特别容易出现在新手的第一个测试文件里。原因就是测试之间共用了同一个数据文件第一个用例写入了数据第二个用例执行时 records.json 里已经有内容了断言自然对不上。解决方法是让数据文件路径可注入。也就是 load_records 和 save_records 设计成能接收一个路径参数测试时传 tmp_path / test.json。我在第41天练习里专门在测试模块里重新定义了一个“测试版”的数据层方法让每个用例都指向临时文件才彻底摆脱了这个诡异问题。这里留一个排查思路出现“单次跑成功、连续跑崩溃”的现象优先怀疑全局状态或共享文件而不是怀疑代码真随机。99% 的情况都是上一次运行留下的残留数据影响了本次判断。4.4 长期上机练习的几条独家经验最后分享几条长期练下来才体味到的经验。第一每次上机时长控制在90到120分钟超过就走神效率骤降。第二中断没那么恐怖但别连续断两天只要隔天没有回补手感马上会退化一大截。第三给每天定一个“最低完成量”哪怕只写5行代码也要打开编辑器执行这种仪式感比写多少行更重要。第四绝对不要用“看别人代码”代替自己动手看懂了离写对还差十万八千里这个感受在练习中途特别深刻。如果你也正在做类似的连续上机练习我的建议是别苛求完美一点点推进。第41天最大的意义在于一个普通人连续琢磨同一件事四十多天这件事已经不只是技能训练而是一份连续的见证。它告诉你把一件事变成日常比天赋更容易带来进步。练完这个记账本下一个阶段就可以考虑把同样的业务逻辑用面向对象重构一遍引入 Decimal、Click 命令行框架等到第60天前后再把数据接到 SQLite 或者做一个简单的 Web 展示页。每一步都是对今天这版代码的自然延伸上机的意义也在于此——你不仅要学会写代码更要学会把一个想法变成可以运行、可以测试、可以分享的东西。这是第41天教会我的事。
返回列表