尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

零基础入门python08:用类和对象重构个人记账本

零基础入门python08:用类和对象重构个人记账本 零基础入门python08用类和对象重构个人记账本一、上一篇课后练习讲解上一篇要求给 JSON 账目增加kind字段。参考实现是新增记录时强制要求kind为income或expense读取旧文件时用record.get(kind, expense)兼容历史数据而不是直接假设每条旧数据都有新字段。可执行验收答案defnormalize_record(record:dict)-dict:kindrecord.get(kind,expense)ifkindnotin{income,expense}:raiseValueError(kind 只能是 income 或 expense)amountfloat(record[amount])ifamount0:raiseValueError(金额不能为负)return{**record,kind:kind,amount:amount}old{amount:12.5,note:午餐}new{amount:100,kind:income,note:工资}assertnormalize_record(old)[kind]expenseassertnormalize_record(new)[kind]incometry:normalize_record({amount:1,kind:gift})exceptValueErrorasexc:print(exc)else:raiseAssertionError(非法 kind 没有失败)升级旧 JSON 时先逐条调用normalize_record发现损坏记录要报告编号并停止写回不要读取一半就用空列表覆盖原文件。二、本篇为什么要学习类当账本只有几个函数时还不难随着读取、添加、统计、保存越来越多函数之间需要共享records和文件路径。类把“数据”和“操作数据的函数”放在一起形成一个有边界的对象。三、完整实现fromdataclassesimportdataclass,asdictfrompathlibimportPathimportjsondataclassclassRecord:一条账目使用 dataclass 自动生成初始化和可读表示。amount:floatkind:strnote:strclassLedger:管理账目生命周期调用方不需要知道 JSON 文件的细节。def__init__(self,path:Path):self.pathpath self.records:list[Record][]self.load()defload(self)-None:ifnotself.path.exists():returndatajson.loads(self.path.read_text(encodingutf-8))self.records[Record(float(x[amount]),x.get(kind,expense),x.get(note,))forxindata]defadd(self,amount:float,kind:str,note:str)-None:ifamount0orkindnotin{income,expense}:raiseValueError(金额必须大于0类型必须是income或expense)self.records.append(Record(amount,kind,note.strip()))defsummary(self)-dict[str,float]:incomesum(x.amountforxinself.recordsifx.kindincome)expensesum(x.amountforxinself.recordsifx.kindexpense)return{income:income,expense:expense,balance:income-expense}defsave(self)-None:payload[asdict(record)forrecordinself.records]self.path.write_text(json.dumps(payload,ensure_asciiFalse,indent2),encodingutf-8)Ledger隐藏了文件读写细节未来换成 SQLite 时命令行层不必重写Record表达一条数据Ledger表达一组数据和业务操作。这就是封装的实际价值。四、运行示例ledgerLedger(Path(records.json))ledger.add(5000,income,工资)ledger.add(35,expense,午餐)ledger.save()print(ledger.summary())预期{income: 5000, expense: 35, balance: 4965}。课后练习增加按月份筛选统计下一篇拆分模块并加入日志调试。实战补充从数据类到账本对象当账本同时拥有记录、文件路径和保存规则时类可以把状态和操作放进一个边界。fromdataclassesimportdataclass,asdictdataclassclassRecord:amount:Decimal kind:strnote:strclassLedger:def__init__(self,path:Path):self.pathpath self.records:list[Record][]self.load()defadd(self,amount:Decimal,kind:str,note:str)-None:ifamount0orkindnotin{income,expense}:raiseValueError(金额必须为正类型只能是 income 或 expense)self.records.append(Record(amount,kind,note.strip()))类的价值不是把函数改成方法而是限制谁能修改内部状态外部只调用 add、summary 和 save未来把 JSON 换成 SQLite 时命令行不必重写。验收与课后练习测试新增收入、支出、非法类型和重新加载课后增加按月份过滤下一篇拆分模块并加入日志。本篇结束完整模块文件本节不是代码片段而是本篇结束时该模块的完整版本。请先备份旧文件再整体替换替换后重新运行本篇命令和测试。阅读时重点看本篇新增的函数、事务边界和错误处理未涉及的代码先不要自行删减。main.pyfromdataclassesimportdataclass,fielddataclassclassOrder:id:intitems:list[str]field(default_factorylist)defadd(self,item):self.items.append(item)orderOrder(1)order.add(Python Book)print(order)
返回列表