题目:定义一个简单的在线购物车类,支持添加商品、删除商品、查看购物车内容以及计算总价的功能。
看到这道题的第一反应:购物车?这不就是我天天在用的东西吗?可要自己用代码写出来,还真得想清楚"一个商品该用什么存、一类东西该怎么管"。
一、我的思路:先想清楚"用什么存"
购物车本质上就是一堆"商品名 → 数量和单价"的对应关系,所以我一眼就想到了字典。
但这里有个细节:光用一层字典不够,因为一个商品同时有"数量"和"单价"两个信息。所以我最终选的是嵌套字典 :外层字典的键是商品名,值是一个小字典,里面放着 quantity 和 price。
self.cart: dictstr, dict\[str, float] = {}
一开始我还有点疑惑:后面那串 : dict[str, dict[str, float]] 是什么?后面才知道,这是类型注解 。
我一开始不写它,PyCharm 会把从字典里取出来的商品信息推断成 Any(也就是"啥都可能"),后面一旦拿去做运算,就会衍生一堆类型警告。显式标注之后,编辑器就知道取出来的值一定是浮点数,世界瞬间清净了。
二、四个方法,各管一件事
类里我写了四个实例方法,正好对应题目的四个功能:
-
add_product()添加商品:这里我处理了一个容易忽略的场景------同名商品重复添加怎么办。我的做法是:如果商品已在购物车里,就把数量累加,单价更新为本次输入的价;如果是新商品,就正常创建一条记录。 -
remove_product()删除商品 :先用in判断商品存不存在,存在就用del删掉并返回True;不存在就提示后返回False,不硬删,避免报错。 -
view_cart()查看购物车 :遍历self.cart.items(),把每个商品的数量、单价、小计一行行打印出来。注意:如果购物车是空的,要单独给一句提示,直接return提前结束方法。 -
calculate_total_price()计算总价 :把每个商品的"数量 × 单价"累加起来。这里我把初始值定为0.0而不是0,这样不管购物车空不空,返回类型都稳定是浮点数。
三、我踩过的坑:差点把购物车写成"input 机器人"
写第一版的时候,我很自然地把 input() 塞进了类的添加、删除方法里,让方法自己问用户要数据。
结果就是:PyCharm 报了我一脸警告(整整 8 个),而且整个类被"提问 - 等待 - 校验"挤得面目全非。
后来我意识到一个原则:类只负责逻辑,菜单只负责交互。
于是我把结构调整成三层:
-
ShoppingCart类:只接收已经校验好的干净数据,专心做增删查算; -
输入辅助函数 :
input_name()、input_quantity()、input_price()三个函数,专门负责问用户要数据并校验; -
主程序菜单 :一个
while True循环打印 12340 选项,用户选什么就调用对应的函数和方法。
这样每一块都只有一个小职责,改任何一块都不影响其他块。
四、输入校验的固定套路:标志位循环
你可能会问:用户输了小数、字母、负数怎么办?我用了一个在之前 101 题里也出现过的套路,就是**"标志位循环 + 循环外统一返回"**:
quantity = 1
valid = False
while not valid:
try:
value = int(input(prompt))
except ValueError:
print(' 输入无效 数量必须是整数。')
continue
if value <= 0:
print(' 输入无效 数量必须是大于 0 的正整数。')
continue
quantity = value
valid = True
return quantity
这里有三个要点:
-
用
try/except接住非数字输入,程序不崩溃,而是重新提示。 -
范围校验放在
try外面用独立if判断,职责更清楚。 -
return写在循环外面,检查器就能认定返回值类型恒定,不会出现"可能返回None"的告警。
五、顺带修正的两个小细节
-
类名复数问题 :第一版我写的是
ShoppingCarts(复数),但一个购物车对象明明是单数,于是修正为ShoppingCart。这种只在文件内部使用的类,改名是安全的。 -
"又打印又返回"的毛病 :原版
calculate_total_price()是自己在方法里打印总价、同时又return的。我把打印拿掉了,方法只负责返回总价,打印交给主程序去做格式化。好处是:这个方法以后想复用到别的地方(比如写到文件、传给别的函数),都不会带着多余的打印行为。
六、小结
这道题练到的东西比表面看起来多:
-
嵌套字典做"一对多属性"的存储结构;
-
__init__里初始化实例属性; -
方法只写逻辑、交互交给主程序菜单的职责分离思想;
-
标志位循环的输入校验套路;
-
以及类型注解消除警告的小技巧。
写完最大的感受是:代码能跑只是及格线,把"谁该干什么"想清楚,才算真正入门了面向对象。