第5章 5.5 综合实战:仿美团外卖小程序
🎯 开场:为什么要学这个?
你有没有遇到过这种情况——
周末在家懒得出门,想点个外卖。打开美团一看,附近 50 家店,怎么快速找到你想吃的那家?是按距离排序?按评分?还是按起送价?
你有没有想过,这个问题背后,其实就是一个「从大量数据里筛选、排序、组合」的问题?
而 Python,就是干这个活儿的。
上一章我们学会了自定义基座和真机调试,把代码跑到了真手机上。但手机上的界面再漂亮,背后支撑它的数据处理逻辑,还是要靠 Python 这样的后端语言来完成。
今天这章,我们用 Python 来「模拟」一个简化版的美团外卖系统——定位、商家列表、购物车、支付,完整流程走一遍。学完你就能明白,一个外卖 App 背后,数据是怎么流转的。
🧱 基础 25 分钟:核心概念
数据结构:商家信息怎么存?
点外卖第一步,你得知道附近有哪些店吧?这些店的信息在程序里怎么存?
举个例子,美团上一家店大概长这样:
- 店名:麦当劳(东方路店)
- \n\n
\n\n
\n\n评分:4.8 分
- 起送价:15 元
- 配送费:3 元
- 距离:1.2 公里
- 招牌菜:巨无霸套餐
在 Python 里,我们用字典(dict)来存这种「名字+值」对应的数据:
# 一家店的信息
restaurant = {
"name": "麦当劳(东方路店)",
"rating": 4.8,
"min_order": 15,
"delivery_fee": 3,
"distance": 1.2,
"signature": "巨无霸套餐"
}
print(restaurant["name"]) # 输出: 麦当劳(东方路店)
print(f"评分: {restaurant['rating']} 分") # 输出: 评分: 4.8 分
类比:字典就像你的通讯录——每个联系人是一个字典,里面有姓名、电话、地址。一个通讯录就是列表套字典。
列表:多家店怎么存?
如果附近有 10 家店,你总不能一家家单独存变量吧?用列表(list)把它们装在一起:
restaurants = [
{"name": "麦当劳", "rating": 4.8, "distance": 1.2, "min_order": 15},
{"name": "肯德基", "rating": 4.6, "distance": 0.8, "min_order": 20},
{"name": "沙县小吃", "rating": 4.2, "distance": 0.5, "min_order": 10},
{"name": "黄焖鸡米饭", "rating": 4.5, "distance": 1.5, "min_order": 15},
]
print(f"共 {len(restaurants)} 家店") # 输出: 共 4 家店
len() 是 Python 内置函数,专门查列表长度。就像你数手指头有几个。
循环:怎么给每家店都「做点什么」?
现在你有了一排店,怎么快速打印出所有店名?
用 for 循环,逐个处理:
restaurants = [
{"name": "麦当劳", "rating": 4.8, "distance": 1.2},
{"name": "肯德基", "rating": 4.6, "distance": 0.8},
{"name": "沙县小吃", "rating": 4.2, "distance": 0.5},
]
for shop in restaurants:
print(f"{shop['name']} - 距离 {shop['distance']}km")
输出:
麦当劳 - 距离 1.2km
肯德基 - 距离 0.8km
沙县小吃 - 距离 0.5km
类比:for 循环就像流水线——传送带过来一个汉堡,你给它套个纸袋;下一个鸡块过来,再套一个。循环就是那个不知疲倦的流水线工人。
筛选:怎么找到我想吃的店?
现在你只想看评分 4.5 以上的店,怎么办?
用 if 判断 过滤:
restaurants = [
{"name": "麦当劳", "rating": 4.8, "distance": 1.2},
{"name": "肯德基", "rating": 4.6, "distance": 0.8},
{"name": "沙县小吃", "rating": 4.2, "distance": 0.5},
{"name": "黄焖鸡米饭", "rating": 4.5, "distance": 1.5},
]
print("评分 4.5 以上的店:")
for shop in restaurants:
if shop["rating"] >= 4.5: # 评分大于等于 4.5
print(f" ✓ {shop['name']} ({shop['rating']}分)")
输出:
评分 4.5 以上的店:
✓ 麦当劳 (4.8分)
✓ 肯德基 (4.6分)
✓ 黄焖鸡米饭 (4.5分)
类比:if 判断就像筛子——把不符合条件的(比如评分低的)漏掉,留下达标的。
排序:怎么按距离从近到远排?
有时候你想找最近的店,用 sorted() 函数:
restaurants = [
{"name": "麦当劳", "distance": 1.2},
{"name": "肯德基", "distance": 0.8},
{"name": "沙县小吃", "distance": 0.5},
{"name": "黄焖鸡米饭", "distance": 1.5},
]
# 按距离从小到大排序
sorted_shops = sorted(restaurants, key=lambda x: x["distance"])
print("按距离排序(从近到远):")
for i, shop in enumerate(sorted_shops, 1):
print(f" {i}. {shop['name']} - {shop['distance']}km")
输出:
按距离排序(从近到远):
1. 沙县小吃 - 0.5km
2. 肯德基 - 0.8km
3. 麦当劳 - 1.2km
4. 黄焖鸡米饭 - 1.5km
key=lambda x: x["distance"] 告诉 Python「按 distance 字段排序」。lambda 是匿名函数,你可以理解为一个「一次性小工具」。
函数:怎么封装「搜索」这个动作?
如果每次搜索都要写一大段代码,太麻烦了。把它封装成函数,下次直接调用:
def find_shops_by_rating(shops, min_rating):
"""查找评分大于等于指定值的店"""
result = []
for shop in shops:
if shop["rating"] >= min_rating:
result.append(shop)
return result
# 模拟数据
restaurants = [
{"name": "麦当劳", "rating": 4.8},
{"name": "沙县小吃", "rating": 4.2},
{"name": "黄焖鸡米饭", "rating": 4.5},
]
# 调用函数
good_shops = find_shops_by_rating(restaurants, 4.5)
print(f"评分 4.5+ 的店: {[s['name'] for s in good_shops]}")
输出:
评分 4.5+ 的店: ['麦当劳', '黄焖鸡米饭']
类比:函数就像自动售货机——你投币(输入参数),机器给你饮料(返回结果)。不用关心里面怎么工作的,用就是了。
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):商家列表展示器
目标:把模拟的商家数据打印成美观的列表。
"""
项目1:商家列表展示器
学习目标:循环 + 格式化输出
"""
# 模拟的美团商家数据
restaurants = [
{"name": "麦当劳", "rating": 4.8, "distance": 1.2, "min_order": 15, "delivery_fee": 3},
{"name": "肯德基", "rating": 4.6, "distance": 0.8, "min_order": 20, "delivery_fee": 2},
{"name": "沙县小吃", "rating": 4.2, "distance": 0.5, "min_order": 10, "delivery_fee": 0},
{"name": "黄焖鸡米饭", "rating": 4.5, "distance": 1.5, "min_order": 15, "delivery_fee": 2},
{"name": "兰州拉面", "rating": 4.3, "distance": 0.6, "min_order": 12, "delivery_fee": 1},
]
print("=" * 50)
print("🍔 附近商家列表")
print("=" * 50)
for i, shop in enumerate(restaurants, 1):
stars = "⭐" * int(shop["rating"])
print(f"\n{i}. {shop['name']}")
print(f" 评分: {stars} {shop['rating']}")
print(f" 距离: {shop['distance']}km | 起送: {shop['min_order']}元 | 配送费: {shop['delivery_fee']}元")
print("\n" + "=" * 50)
预期输出:
==================================================
🍔 附近商家列表
==================================================
1. 麦当劳
分: ⭐⭐⭐⭐⭐ 4.8
离: 1.2km | 起送: 15元 | 配送费: 3元
2. 肯德基
分: ⭐⭐⭐⭐ 4.6
离: 0.8km | 起送: 20元 | 配送费: 2元
...(省略)
一句话解释:用 enumerate 给列表加序号,格式化字符串让输出更美观。
项目 2(15 分钟):购物车系统
目标:实现「选商品 → 加购物车 → 计算总价」的完整流程。
"""
项目2:外卖购物车系统
学习目标:字典操作 + 数量统计 + 价格计算
"""
# 模拟商家菜单
menu = {
"巨无霸套餐": {"price": 35, "stock": 10},
"麦乐鸡块(5块)": {"price": 18, "stock": 15},
"可口可乐(中)": {"price": 8, "stock": 20},
"薯条(大)": {"price": 12, "stock": 18},
}
# 购物车:商品 -> 数量
cart = {}
def add_to_cart(item, quantity=1):
"""添加商品到购物车"""
if item not in menu:
print(f"❌ 没有这个商品: {item}")
return False
if menu[item]["stock"] < quantity:
print(f"❌ 库存不足: {item},只剩 {menu[item]['stock']} 份")
return False
if item in cart:
cart[item] += quantity
else:
cart[item] = quantity
print(f"✅ 已添加 {item} x{quantity} 到购物车")
return True
def remove_from_cart(item, quantity=1):
"""从购物车移除商品"""
if item not in cart:
print(f"❌ 购物车里没有: {item}")
return False
if cart[item] <= quantity:
del cart[item]
print(f"🗑️ 已移除全部 {item}")
else:
cart[item] -= quantity
print(f"🗑️ 已移除 {item} x{quantity}")
return True
def show_cart():
"""显示购物车内容"""
if not cart:
print("🛒 购物车是空的")
return
print("\n" + "=" * 40)
print("🛒 购物车内容")
print("=" * 40)
total = 0
for item, qty in cart.items():
price = menu[item]["price"]
subtotal = price * qty
total += subtotal
print(f" {item} x{qty} = {subtotal}元")
print("-" * 40)
print(f" 💰 总计: {total} 元")
print("=" * 40)
def checkout():
"""结账"""
if not cart:
print("🛒 购物车是空的,无法结算")
return
# 计算总价
total = sum(menu[item]["price"] * qty for item, qty in cart.items())
# 扣减库存
for item, qty in cart.items():
menu[item]["stock"] -= qty
print(f"\n🎉 下单成功!实付 {total} 元")
cart.clear() # 清空购物车
# ===== 测试流程 =====
print("【添加商品】")
add_to_cart("巨无霸套餐")
add_to_cart("麦乐鸡块(5块)", 2)
add_to_cart("可口可乐(中)", 3)
print("\n【查看购物车】")
show_cart()
print("\n【移除商品】")
remove_from_cart("可口可乐(中)")
print("\n【再次查看购物车】")
show_cart()
print("\n【结账】")
checkout()
print("\n【结账后查看购物车】")
show_cart()
预期输出:
【添加商品】
✅ 已添加 巨无霸套餐 x1 到购物车
✅ 已添加 麦乐鸡块(5块) x2 到购物车
✅ 已添加 可口可乐(中) x3 到购物车
【查看购物车】
========================================
🛒 购物车内容
========================================
巨无霸套餐 x1 = 35元
麦乐鸡块(5块) x2 = 36元
可口可乐(中) x3 = 24元
----------------------------------------
💰 总计: 95 元
========================================
【移除商品】
🗑️ 已移除 可口可乐(中)
【再次查看购物车】
========================================
🛒 购物车内容
========================================
巨无霸套餐 x1 = 35元
麦乐鸡块(5块) x2 = 36元
----------------------------------------
💰 总计: 71 元
========================================
【结账】
🎉 下单成功!实付 71 元
【结账后查看购物车】
🛒 购物车是空的
一句话解释:购物车用字典存商品和数量,结账时遍历购物车计算总价,然后清空。
项目 3(15 分钟):智能推荐系统
目标:根据用户偏好(预算、评分要求),从多家店中筛选最合适的。
"""
项目3:智能商家推荐系统
学习目标:综合运用筛选 + 排序 + 函数封装
"""
# 模拟商家数据(含更多字段)
restaurants = [
{"name": "麦当劳", "rating": 4.8, "distance": 1.2, "min_order": 15, "delivery_fee": 3, "cuisine": "快餐"},
{"name": "肯德基", "rating": 4.6, "distance": 0.8, "min_order": 20, "delivery_fee": 2, "cuisine": "快餐"},
{"name": "沙县小吃", "rating": 4.2, "distance": 0.5, "min_order": 10, "delivery_fee": 0, "cuisine": "小吃"},
{"name": "黄焖鸡米饭", "rating": 4.5, "distance": 1.5, "min_order": 15, "delivery_fee": 2, "cuisine": "中餐"},
{"name": "兰州拉面", "rating": 4.3, "distance": 0.6, "min_order": 12, "delivery_fee": 1, "cuisine": "面馆"},
{"name": "川菜馆", "rating": 4.7, "distance": 2.0, "min_order": 30, "delivery_fee": 4, "cuisine": "中餐"},
{"name": "日料店", "rating": 4.9, "distance": 1.8, "min_order": 50, "delivery_fee": 5, "cuisine": "日料"},
]
def filter_restaurants(shops, **kwargs):
"""
多条件筛选商家
kwargs 支持: min_rating, max_distance, max_min_order, cuisine
"""
result = shops
if "min_rating" in kwargs:
result = [s for s in result if s["rating"] >= kwargs["min_rating"]]
if "max_distance" in kwargs:
result = [s for s in result if s["distance"] <= kwargs["max_distance"]]
if "max_min_order" in kwargs:
result = [s for s in result if s["min_order"] <= kwargs["max_min_order"]]
if "cuisine" in kwargs:
result = [s for s in result if s["cuisine"] == kwargs["cuisine"]]
return result
def recommend_by_score(shops, weights=None):
"""
计算综合评分并排序
评分公式 = 评分*权重1 + 近距离*权重2 + 低起送*权重3
"""
if weights is None:
weights = {"rating": 0.5, "distance": 0.3, "min_order": 0.2}
scored = []
for shop in shops:
# 归一化处理(简单版)
score = (
shop["rating"] / 5.0 * weights["rating"] +
(3 - shop["distance"]) / 3 * weights["distance"] + # 距离越近分数越高
(50 - shop["min_order"]) / 50 * weights["min_order"] # 起送价越低分数越高
)
shop["score"] = round(score, 3)
scored.append(shop)
# 按综合评分排序
return sorted(scored, key=lambda x: x["score"], reverse=True)
def display_recommendations(shops, top_n=3):
"""展示推荐结果"""
print(f"\n🏆 为您推荐 TOP {min(top_n, len(shops))} 家店:")
print("-" * 60)
for i, shop in enumerate(shops[:top_n], 1):
print(f"{i}. {shop['name']} ({shop['cuisine']})")
print(f" 综合评分: {shop['score']} | 评分: {shop['rating']}⭐ | 距离: {shop['distance']}km")
print(f" 起送: {shop['min_order']}元 | 配送费: {shop['delivery_fee']}元")
# ===== 用户偏好设置 =====
user_budget = 20 # 预算 20 元起送
user_rating = 4.3 # 想要评分 4.3 以上的
max_distance = 2.0 # 距离不超过 2km
print(f"📍 您的偏好:评分≥{user_rating} | 距离≤{max_distance}km | 预算起送{user_budget}元")
# 第一步:筛选
filtered = filter_restaurants(
restaurants,
min_rating=user_rating,
max_distance=max_distance,
max_min_order=user_budget
)
print(f"\n📊 初步筛选出 {len(filtered)} 家符合条件的店")
# 第二步:计算综合评分并排序
ranked = recommend_by_score(filtered)
# 第三步:展示推荐
display_recommendations(ranked, top_n=3)
预期输出:
📍 您的偏好:评分≥4.3 | 距离≤2.0km | 预算起送20元
📊 初步筛选出 4 家符合条件的店
🏆 为您推荐 TOP 3 家店:
------------------------------------------------------------
1. 兰州拉面 (面馆)
合评分: 0.833 | 评分: 4.3⭐ | 距离: 0.6km
送: 12元 | 配送费: 1元
2. 黄焖鸡米饭 (中餐)
合评分: 0.824 | 评分: 4.5⭐ | 距离: 1.5km
送: 15元 | 配送费: 2元
3. 麦当劳 (快餐)
合评分: 0.81 | 评分: 4.8⭐ | 距离: 1.2km
送: 15元 | 配送费: 3元
一句话解释:**kwargs 让函数接收任意数量的关键字参数,实现灵活的多条件筛选。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:修改列表时别同时循环
# ❌ 错误示范:一边删一边循环,索引会乱
fruits = ["苹果", "香蕉", "樱桃", "香蕉", "香蕉"]
for fruit in fruits:
if fruit == "香蕉":
fruits.remove(fruit) # 可能漏删或报错
# ✅ 正确做法:用列表推导式生成新列表
fruits = ["苹果", "香蕉", "樱桃", "香蕉", "香蕉"]
fruits = [f for f in fruits if f != "香蕉"]
print(fruits) # ['苹果', '樱桃']
原因:循环时删除元素,列表长度变了,但索引没变,就像抽掉一截的多米诺骨牌,后面的全乱了。
坑 2:字典 key 不存在会报错
# ❌ 错误示范
shop = {"name": "麦当劳"}
print(shop["rating"]) # KeyError: 'rating'
# ✅ 正确做法:用 get() 方法,设置默认值
shop = {"name": "麦当劳"}
print(shop.get("rating", "暂无评分")) # 输出: 暂无评分
原因:get() 像一个贴心的服务员,问你要的东西没有,它不会甩脸色,而是给你一个默认值。
坑 3:浮点数比较别用 ==
# ❌ 错误示范
result = 0.1 + 0.2
print(result == 0.3) # False!
# ✅ 正确做法:容差比较
result = 0.1 + 0.2
print(abs(result - 0.3) < 0.0001) # True
原因:计算机表示小数有精度问题,0.1 + 0.2 实际是 0.30000000000000004。
坑 4:字符串拼接别用 + 号
# ❌ 低效做法
html = ""
for i in range(1000):
html += f"<div>{i}</div>"
# ✅ 高效做法:用 join
parts = [f"<div>{i}</div>" for i in range(1000)]
html = "".join(parts)
原因:+ 每次都创建新字符串再复制,1000 次就是 1000 次内存分配。join() 是一次性搞定。
坑 5:函数默认参数别用可变对象
# ❌ 错误示范
def add_item(item, cart=[]):
cart.append(item)
return cart
print(add_item("汉堡")) # ['汉堡']
print(add_item("可乐")) # ['汉堡', '可乐'] ← 之前的还在!
# ✅ 正确做法:默认 None,函数内初始化
def add_item(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cart
print(add_item("汉堡")) # ['汉堡']
print(add_item("可乐")) # ['可乐'] ← 干净的
调试技巧:print 大法
# 复杂逻辑不知道哪错了?分段 print
def calculate_total(cart, menu):
total = 0
for item, qty in cart.items():
price = menu[item]["price"]
print(f"DEBUG: {item} x{qty} = {price} x {qty}") # 加这行
total += price * qty
print(f"DEBUG: 当前 total = {total}") # 加这行
return total
技巧:print("DEBUG: ...") 是最简单的调试方式,变量走到哪一步、值是什么,一目了然。
✏️ 练习题 + 作业题
练习题(10 分钟)
练习 1(2 分钟):打印商家数量
- 输入:restaurants 列表(4家店)
- 预期输出:共 4 家店可选
- 提示:len() 函数
练习 2(2 分钟):筛选平价店
- 输入:添加一个 if 判断,筛选起送价 ≤ 15 元的店
- 预期输出:只打印符合条件的店
- 提示:if shop["min_order"] <= 15
练习 3(2 分钟):购物车加商品
- 输入:调用 add_to_cart("薯条(大)", 2)
- 预期输出:✅ 已添加 薯条(大) x2 到购物车
- 提示:直接运行项目 2 的代码
练习 4(2 分钟):修改推荐权重
- 输入:把评分权重从 0.5 改成 0.8
- 预期输出:推荐顺序可能变化
- 提示:修改 weights 字典
练习 5(2 分钟):找出报错原因
- 输入:运行 print(shop["不存在的键"])
- 预期输出:KeyError 报错
- 提示:用 get() 避免报错
作业题(30 分钟-2 小时)
作业:做一个「外卖订单小助手」
需求描述:模拟一个外卖点餐流程,用户可以浏览商家、选购商品、修改数量、结账。
功能点:
1. 至少有 3 家不同店铺,每家店有 3-5 个菜品
2. 用户可以选择店铺、添加菜品到购物车
3. 支持修改购物车商品数量(+1/-1)
4. 结账时显示订单详情(含配送费计算)
加分项:
1. 用函数封装「选择店铺」「添加购物车」「结账」等操作
2. 支持删除购物车中的商品
验收标准:
- 能运行不报错
- 输出清晰易懂
- 代码有适当注释
📚 总结 + 资源
本文学了 3 件事:
1. 数据结构:字典存单条信息,列表存多条数据
2. 流程控制:for 循环遍历、if 判断筛选
3. 函数封装:把重复操作封装成函数,代码更整洁
延伸学习资源:
- 官方文档:Python 官方教程 — 最权威的入门资料
- 书籍:《Python编程:从入门到实践》— 边学边做项目
- 视频:B 站「小甲鱼 Python 教程」— 视频讲解更适合新手
互动钩子:你在写外卖系统的时候,有没有遇到过什么奇葩 bug?比如购物车数量变成负数、订单金额算错了?评论区聊聊,老粉优先回复!
📌 下章预告:学会了数据处理,下一章我们要把它搬到浏览器里——「第6章 6.1 H5 端开发」,用 HTML/CSS/JS 做一个能在浏览器里跑的外卖页面。敬请期待!

评论(0)