第5章 5.5 综合实战:仿美团外卖小程序

🎯 开场:为什么要学这个?

你有没有遇到过这种情况——

周末在家懒得出门,想点个外卖。打开美团一看,附近 50 家店,怎么快速找到你想吃的那家?是按距离排序?按评分?还是按起送价?

你有没有想过,这个问题背后,其实就是一个「从大量数据里筛选、排序、组合」的问题

而 Python,就是干这个活儿的。

上一章我们学会了自定义基座和真机调试,把代码跑到了真手机上。但手机上的界面再漂亮,背后支撑它的数据处理逻辑,还是要靠 Python 这样的后端语言来完成

今天这章,我们用 Python 来「模拟」一个简化版的美团外卖系统——定位、商家列表、购物车、支付,完整流程走一遍。学完你就能明白,一个外卖 App 背后,数据是怎么流转的


🧱 基础 25 分钟:核心概念

数据结构:商家信息怎么存?

点外卖第一步,你得知道附近有哪些店吧?这些店的信息在程序里怎么存?

举个例子,美团上一家店大概长这样:
- 店名:麦当劳(东方路店)
- \n\nSimple tech illustration expla\n\nAI comic creation scene, creat\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 做一个能在浏览器里跑的外卖页面。敬请期待!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。