资讯动态

Python练习项目:手把手实现控制台购物车程序

发布时间:2026/10/15 16:56:34 来源:尧图企业网站定制
1. 需求拆解与设计思路很多人学完了 Python 基础语法之后会陷入同一个困惑语法书翻了不少例题也敲了可一到自己动手写个像样的程序脑子里还是空的。我遇到这种情况通常会先建议对方写一个简化版购物车听起来不起眼但它几乎是控制台小程序里最值得练手的题目之一。它不要求任何第三方库却能逼着你把变量、列表、字典、循环、条件判断、函数封装、异常处理全部串起来而且做完之后能立刻看到一个软件的样子不是那种跑完就结束的课后习题。购物车这个场景大家都不陌生。去电商网站买东西把商品放进购物车改个数量删掉不想要的最后结算付款。落到命令行程序里其实就是几个反复交互的操作。我给它划定的功能边界是这样的能展示商品列表、能往购物车里加商品、能修改购物车里某个商品的数量、能删除购物车里的商品、能计算最终应付金额、能处理用户的非法输入。这六件事做完这个程序就可以拿去交作业了。1.1 核心需求拆成模块去思考拿到题目先别着急写代码把需求拆开这个过程比写代码本身还锻炼人。购物车程序的全部流程可以抽象成下面几段程序启动后进入一个主菜单用户通过数字选择要做的操作选择查看商品就把商品列表打印出来选择加购就要判断用户输入的商品编号是否存在、要买几个选择修改数量就要判断用户想改哪个商品、改成多少选择删除同理最后点结算程序遍历购物车里的每一个条目把单价乘数量累加起来再考虑优惠规则最终输出金额。把流程拆清楚之后写代码的思路就变成了一个主循环负责不断显示菜单并读取用户选择然后根据选择调用对应函数。每个函数只做一件事这样代码可读性高后面想加功能也不会越改越乱。1.2 为什么购物车题目适合练手这个题目的好处在于它的数据关系非常直观。商品是商品购物车是购物车每个购物车条目包含哪个商品和买了几个这样的信息。用错数据结构代码很快就绕晕用对数据结构每一步都顺理成章。学编程最怕的就是概念背了一大堆但不知道用在哪购物车这道题恰好能把列表和字典的配合、函数参数传递、返回值、作用域这些概念全部落在实处。我也见过很多初学者把购物车写成一个大面条所有逻辑堆在 while 循环里menu 一个又一个 elif。这个写法不是不能跑而是跑到后面很难维护。我在这篇文章里会从数据结构选型讲起逐步搭建一个结构清晰的控制台购物车最后给出完整代码和运行效果顺便分享几个我实际踩过的坑。1.3 先定义操作菜单第一版菜单我通常设计成下面这样1. 查看商品 2. 加入购物车 3. 修改商品数量 4. 删除商品 5. 查看购物车 6. 结算 0. 退出菜单确定了主循环的骨架也就确定了。这里有一个关键点用户输入的是一个数字但程序不能假设用户一定会乖乖输入 1 到 6。如果用户敲了 9 甚至敲了一串字母程序必须能给出友好提示而不是直接崩溃。这个健壮性要求看起来小但正式项目里同样重视所以从一开始就养成处理异常输入的习惯很有必要。2. 数据结构选型与准备工作购物车程序的难点不在语法而在如何选择数据结构来组织数据。我见过有人用三个平行列表分别存商品编号、商品名称、商品价格结果加一个商品就要三处同时 append删一个商品也要三处同时删特别容易漏。这种别扭感其实是数据结构没选好导致的。2.1 商品信息用嵌套字典对于商品信息我推荐用字典key 是商品编号value 是另一个字典里面存名称和单价。比如products { 1: {name: 签字笔, price: 2.5}, 2: {name: 笔记本, price: 3.2}, 3: {name: 矿泉水, price: 1.5}, 4: {name: 抽纸, price: 12.9}, }这样设计的好处很直接当我们需要查找编号为 2 的商品时用products[2]一步就拿到它对应的字典不需要遍历。字典的 key 是从 1 开始的整数天然就和用户输入的编号一一对应代码读起来也直观。可能有同学会问为什么不用列表列表当然也可以存商品比如products [{id:1, name:..., price:...}, ...]但查找的时候要遍历还要额外保存 id 字段。而且后续如果要做按编号删除商品列表会涉及元素下标的重新整理容易出错。反观字典删除一个 key 就是一行products.pop(1)插入新商品也只需要products[5] {...}语义非常清晰。2.2 购物车数据用列表套字典购物车本身我推荐用列表列表里的每个元素是一个小字典记录商品编号和数量。比如cart [ {pid: 1, num: 3}, {pid: 4, num: 2}, ]看到这里你可能会想为什么购物车不也用字典比如直接用cart {1: 3, 4: 2}把商品编号映射到数量不是更省事吗确实用{商品编号: 数量}这个结构在某些场景下很方便加够同一件商品时直接改数字就行。但用列表套字典的方式也有它的理由。第一列表能够天然表达购买顺序商品是按什么顺序加进来的遍历时就是什么顺序第二后续如果你想扩展购物车字段比如加一个是否已勾选参与优惠的标记字典结构仍然能扩展而纯{pid: num}结构就很难添加这类附加属性。从编码教学的角度看列表套字典能让初学者同时练习两种核心容器的配合收益更大。2.3 辅助变量与全局设计在这个程序里我会把商品表和购物车都定义成全局变量。一些书籍会强调尽量避免全局变量但对于一个小程序而言全局变量反而最直观也更方便在函数之间共享状态。如果后面要重构也可以把这两样东西收进一个购物车类中这类话题我在最后一节会展开。程序还需要一个变量来控制主循环的运行通常是while True: # 显示菜单 # 读用户选择 # 处理选择用户输入 0 时用break退出循环。整个控制流就这么简单。3. 核心功能实现结构定好了接下来就是一个个函数往里面填。我会把每个函数的输入输出理清楚然后给出可复用的写法。3.1 展示商品列表展示商品列表是最简单的功能不需要任何参数直接遍历全局的 products 字典格式化输出即可。我习惯用f-string把编号、名称、价格排成对齐的列def show_products(): print(商品列表) for pid, info in products.items(): print(f{pid}. {info[name]} -- {info[price]:.2f} 元):.2f是保留两位小数的格式化语法很有用。价格这种东西如果不控制小数位可能会出现 12.9 显示成 12.900000000000002 这种可怕局面。我在后面还会专门讲浮点数精度问题这里先记住一点金额显示一定要格式化。3.2 加入购物车加购函数需要接收用户输入的商品编号和数量然后把数据写进购物车。核心逻辑分两步第一步判断商品编号是否存在不存在就提示并返回第二步判断这个商品是否已经在购物车里如果在就把数量累加否则追加一个新条目。def add_to_cart(pid, num): if num 0: print(购买数量必须大于0) return for item in cart: if item[pid] pid: item[num] num print(f已将 {num} 件商品加入购物车) return cart.append({pid: pid, num: num}) print(f已将 {num} 件商品加入购物车)注意for item in cart这里的 item 拿到的是列表里每个字典的引用所以直接修改item[num]就等于修改了 cart 里的数据。如果不太理解引用这个概念可以暂时记住结论for遍历列表时循环变量指向列表里的元素本身元素是可变对象时可以直接修改。有一个细节要特别提一下数量小于等于 0 的情况必须拦截。有些初学者只写了等于 0 的判断忽略了负数结果用户输入 -5 也能加购最后结算金额变成负数非常搞心态。输入校验永远不要嫌多。3.3 修改数量与删除商品修改购物车数量本质上就是找到对应条目然后重新赋值。我依然用 for 循环遍历找到匹配的 pid 后让用户输入新数量。如果新数量大于 0就覆盖旧值如果等于 0 或者小于 0干脆提示用户可以走删除功能这样语义上更清晰。def modify_cart(pid, new_num): for item in cart: if item[pid] pid: if new_num 0: print(数量必须大于0如需删除请选择删除功能) return item[num] new_num print(修改成功) return print(购物车中未找到该商品)删除功能分两种情况按商品编号删除或者清空整个购物车。按编号删除要特别小心不能在遍历列表的过程中直接调用cart.remove(item)因为那样会改变列表长度可能导致一个元素被跳过。正确做法是先记住要删的条目遍历结束后再删除或者干脆先找出下标再pop(index)。def remove_from_cart(pid): for i, item in enumerate(cart): if item[pid] pid: cart.pop(i) print(删除成功) return print(购物车中未找到该商品)用enumerate拿到下标和元素在确认找到后 pop这样安全且直观。我见过不少同学直接在for item in cart:里执行cart.remove(item)跑起来偶尔会跳过相邻元素原因就是在遍历中修改了正在遍历的列表。这个坑一定要提前避开。3.4 结算与优惠计算结算函数的任务是遍历购物车把每一项的单价乘数量累加起来。注意单价要拿到商品表里去查购物车只存编号和数量不直接存单价。为什么不直接存单价因为商品表才是价格的唯一事实来源。如果以后商品调价购物车里的历史价格也应该跟着变这样设计才合理。def checkout(): if not cart: print(购物车是空的先去逛逛吧) return total 0.0 for item in cart: info products[item[pid]] subtotal info[price] * item[num] print(f{info[name]} x {item[num]} {subtotal:.2f} 元) total subtotal if total 200: total - 30 print(满200元减30元已优惠30元) elif total 100: total - 10 print(满100元减10元已优惠10元) print(f应付金额{total:.2f} 元)这里我加了一个很常见的满减优惠规则满 100 减 10满 200 减 30。这种业务规则放在结算函数里非常贴近真实需求。你可以按自己的喜好改成打折或者包邮思路都一样先算原价总额再判断规则再输出结果。一个需要小心的地方是if not cart判断。空列表是 falsy 值所以not cart为真能准确处理结算空购物车的情况。如果不做这个判断遍历空列表不会报错但后面你可能会想展示一个空购物车提示还是提前拦截更好。4. 完整代码与运行效果前面分模块把函数一个个讲清楚现在把它们拼起来形成完整可运行的脚本。我平时给朋友演示的时候会直接把整段代码贴出来跑一遍给他们看效果比单纯讲理论好得多。4.1 完整购物车程序products { 1: {name: 签字笔, price: 2.5}, 2: {name: 笔记本, price: 3.2}, 3: {name: 矿泉水, price: 1.5}, 4: {name: 抽纸, price: 12.9}, 5: {name: 保温杯, price: 59.0}, } cart [] def show_products(): print(商品列表) for pid, info in products.items(): print(f{pid}. {info[name]} -- {info[price]:.2f} 元) def show_cart(): if not cart: print(购物车是空的) return print(当前购物车) for item in cart: info products[item[pid]] print(f{info[name]} x {item[num]}小计 {info[price] * item[num]:.2f} 元) def add_to_cart(pid, num): if pid not in products: print(商品编号不存在) return if num 0: print(购买数量必须大于0) return for item in cart: if item[pid] pid: item[num] num print(f已将 {num} 件 {products[pid][name]} 加入购物车) return cart.append({pid: pid, num: num}) print(f已将 {num} 件 {products[pid][name]} 加入购物车) def modify_cart(pid, new_num): if new_num 0: print(数量必须大于0如需删除请选择删除功能) return for item in cart: if item[pid] pid: item[num] new_num print(修改成功) return print(购物车中未找到该商品) def remove_from_cart(pid): for i, item in enumerate(cart): if item[pid] pid: cart.pop(i) print(删除成功) return print(购物车中未找到该商品) def checkout(): if not cart: print(购物车是空的先去逛逛吧) return total 0.0 print(结算明细) for item in cart: info products[item[pid]] subtotal info[price] * item[num] print(f{info[name]} x {item[num]} {subtotal:.2f} 元) total subtotal if total 200: total - 30 print(满200元减30元已优惠30元) elif total 100: total - 10 print(满100元减10元已优惠10元) print(f应付金额{total:.2f} 元) def main(): while True: print(\n 购物车菜单 ) print(1. 查看商品) print(2. 加入购物车) print(3. 修改商品数量) print(4. 删除商品) print(5. 查看购物车) print(6. 结算) print(0. 退出) try: choice int(input(请选择操作)) except ValueError: print(请输入数字) continue if choice 0: print(欢迎下次光临) break elif choice 1: show_products() elif choice 2: show_products() try: pid int(input(请输入商品编号)) num int(input(请输入购买数量)) except ValueError: print(请输入合法的数字) continue add_to_cart(pid, num) elif choice 3: if not cart: print(购物车是空的先加购吧) continue show_cart() try: pid int(input(请输入要修改的商品编号)) new_num int(input(请输入新的数量)) except ValueError: print(请输入合法的数字) continue modify_cart(pid, new_num) elif choice 4: if not cart: print(购物车是空的无需删除) continue show_cart() try: pid int(input(请输入要删除的商品编号)) except ValueError: print(请输入合法的数字) continue remove_from_cart(pid) elif choice 5: show_cart() elif choice 6: checkout() else: print(无效选项请重新输入) if __name__ __main__: main()4.2 运行效果模拟假设用户依次执行查看商品加入购物车查看购物车结算控制台输出大致如下 购物车菜单 1. 查看商品 ... 请选择操作1 商品列表 1. 签字笔 -- 2.50 元 2. 笔记本 -- 3.20 元 3. 矿泉水 -- 1.50 元 4. 抽纸 -- 12.90 元 5. 保温杯 -- 59.00 元 请选择操作2 商品列表 ... 请输入商品编号5 请输入购买数量2 已将 2 件 保温杯 加入购物车 请选择操作5 当前购物车 保温杯 x 2小计 118.00 元 请选择操作6 结算明细 保温杯 x 2 118.00 元 满100元减10元已优惠10元 应付金额108.00 元从代码里也能看到我在主循环里对用户输入的数字都做了try...except ValueError处理。你可能会觉得这有点啰嗦但真实场景里用户就是会输乱七八糟的东西不加这层保护程序一个ValueError就崩了体验极差。5. 常见问题与排查技巧实录这个购物车程序我几乎每年都会带新一轮同学写一遍反复踩的坑就那么几个。我把它们整理成速查表写代码前看一眼能省很多调试时间。5.1 遍历时删除元素导致跳项前面提过一次但这里还是值得单独拿出来说因为它太经典了。错误的删除写法如下for item in cart: if item[pid] pid: cart.remove(item)假设购物车里有三条记录你删除第一条时Python 会把第二条顶上来到第一个位置循环接着从原第二个位置开始遍历此时它访问到的是原第三条原第二条就被跳过了。结果可能出现明明删了一个商品界面上却少了两条的错觉。正确写法是for i, item in enumerate(cart)后通过下标 pop。5.2 浮点数金额精度问题Python 的浮点运算是二进制近似的0.1 0.2在屏幕上显示出来的结果可能是0.30000000000000004。在购物车这种涉及金钱计算的场景里如果不处理累加多件商品后金额可能带出一长串小数点。我通常提供两种解决思路。第一种结算时统一用round(total, 2)或者格式化输出f{total:.2f}让显示层看起来正常。第二种更严谨把元换算成分以整数分存储和计算只在最后展示时转换成元。比如 12.9 元存成 1290 分所有加法都是整数不会产生精度问题。price_in_fen round(price * 100) total_fen sum(...) print(f应付金额{total_fen / 100:.2f} 元)实际应用里涉及金钱的软件大多采用整数分存储这是个好习惯做购物车题目时就可以提前建立这种意识。5.3 列表与字典的浅拷贝误解如果你打算实现用户再次确认购物车的功能可能会写出类似temp_cart cart的操作。这里要特别注意temp_cart cart不是复制列表而是让temp_cart和cart指向同一个列表对象。修改temp_cart时cart的数据也会跟着变。如果真的需要复制应该用cart.copy()或者copy.deepcopy(cart)。对于购物车这种列表里有字典的结构cart.copy()属于浅拷贝它复制了列表本身但列表里的字典还是那些字典。如果你要修改字典内部的字段原始 cart 里的字典也会变。这种微妙差别第一次接触时很容易踩坑建议自己写几行代码验证一下印象会非常深。5.4 编号不存在时直接报 KeyErrorproducts[pid]在 pid 不存在时会抛KeyError。很多初学者的第一版代码没处理这个问题用户输入一个不存在的编号 99程序直接红字崩溃。解决办法有两个一是查询前先检查if pid not in products:二是用字典的get方法比如info products.get(pid)如果 pid 不存在info 会是None再做一层判断即可。我更推荐第一种写法原因是它的意图更直白阅读代码的人一看就懂。get在需要提供默认值的场合更合适比如products.get(pid, {name: 未知商品, price: 0})。5.5 while 循环中的 continue 滥用主循环里我用了continue来跳过本次后续代码回到菜单。有些同学会不小心把continue写在错误的位置导致某些操作还没做完就跳回菜单看起来像是程序出了 bug。排查这类问题时可以在每个分支里打印一行调试日志比如print(正在执行删除逻辑)然后看日志顺序就能快速定位是哪一步触发的 continue。6. 扩展方向与个人建议一个简单的购物车程序完成后如果还想继续深入可以按下面几个方向扩展。每个方向都能让你在 Python 编程上更进一步而且成就感是递增的。6.1 增加数据持久化把购物车存到文件当前的购物车数据只存在于内存里程序一关就全没了。这显然不真实。你可以把购物车序列化成 JSON 字符串保存到本地文件程序启动时再读回来。import json def save_cart(): with open(cart.json, w, encodingutf-8) as f: json.dump(cart, f, ensure_asciiFalse) def load_cart(): global cart try: with open(cart.json, r, encodingutf-8) as f: cart json.load(f) except FileNotFoundError: cart []这十几行代码就把程序从一次性的玩具变成了可以反复使用的工具。JSON 是所有现代编程语言都支持的格式学会用它做配置和数据交换对后面的项目开发帮助非常大。很多人以为文件持久化很难其实有了json模块几乎就是读和写两个操作的事。6.2 用面向对象思想重构如果已经理解了函数版购物车下一步我强烈建议用类来改写它。核心思路是把购物车本身定义成一个类商品表和购物车条目都变成实例属性加购、删减、结算都变成方法class ShoppingCart: def __init__(self, products): self.products products self.items [] def add(self, pid, num): ... def remove(self, pid): ... def checkout(self): ...这样重构之后全局变量的数量会大大减少程序的扩展性也会明显提升。比如在同一份代码里你甚至可以创建两个购物车对象一个模拟普通用户购物车一个模拟 VIP 用户购物车互不干扰。这种从函数进化到类的过程其实就是理解面向对象编程最自然的路径。6.3 增加更丰富的业务规则满减规则可以写得更灵活。比如满 200 减 30 和满 300 减 50 同时存在取优惠最大的规则再比如某些商品不参与优惠需要给商品字典加一个is_discounted字段或者计算运费不满 49 元要加 6 元邮费满 49 元包邮。这些规则在真实电商系统里非常常见加进你的购物车程序后结算逻辑会变得更有挑战性。我建议不要一口气把规则全加上而是每加一条就运行一次看看前面的代码有没有被影响。这种小步修改、频繁验证的习惯对以后写大型项目至关重要。6.4 我的最终心得在给别人讲购物车项目这件事上我的体会是越简单的项目越能暴露出基础知识是否扎实。列表和字典的配合、循环里修改变量、异常输入处理、浮点金额精度这些细节单独拿出来都不难但放在一个真实的应用场景里就会交叉纠缠。能够把购物车这个项目写得既完整又干净的人写其他增删改查类的小项目通常也不会太吃力。如果你正在学 Python别小看这样一个命令行小程序。用一晚上的时间把它写完再对照这篇文章里的坑自查一遍一定会比单纯刷十道语法题收获更大。后面无论你是要往数据分析、后端开发还是爬虫方向走这些基础中锤炼出来的逻辑能力都会成为你最稳的底子。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑