本文是《企业级自动化测试实战》系列的第 3 篇。上一篇我们学习了流程控制和函数,编写了 MallLite 的商品查询、搜索、分页、购物车等核心函数。本篇将深入讲解 Python 的四大核心数据结构,然后用它们为 MallLite 编写订单管理和数据统计模块。


前言

自动化测试中,几乎所有的操作都在和数据打交道:接口返回的是 JSON(本质是字典和列表的嵌套),Excel 读出来的是二维表格(列表套列表),断言时要从层层嵌套的响应中提取某个字段值。

如果数据结构不熟练,后面写自动化测试时会非常痛苦——光是 response["data"]["items"][0]["name"] 这种嵌套取值就能把人绕晕。

今天的任务:

  • 深入掌握列表(list)的高级操作
  • 深入掌握字典(dict)的高级操作
  • 理解元组(tuple)和集合(set)
  • 掌握字符串的更多技巧
  • 为 MallLite 编写订单管理函数
  • 为 MallLite 编写数据统计模块

一、列表 list 深入

列表是 Python 中最常用的数据结构,本质是一个有序的、可变的容器。

1.1 列表的创建

# 直接创建
fruits = ["苹果", "香蕉", "橘子"]

# 空列表
empty = []

# 用 range 生成数字列表
numbers = list(range(1, 11))  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 用列表推导式生成
squares = [x ** 2 for x in range(1, 6)]  # [1, 4, 9, 16, 25]

1.2 列表的增删改查

products = ["iPhone", "MacBook", "iPad"]

# ====== 增 ======
products.append("AirPods")          # 末尾追加 → ["iPhone", "MacBook", "iPad", "AirPods"]
products.insert(1, "Huawei")        # 在索引 1 处插入 → ["iPhone", "Huawei", "MacBook", "iPad", "AirPods"]
products.extend(["Xiaomi", "OPPO"]) # 批量追加 → [..., "Xiaomi", "OPPO"]

# ====== 删 ======
products.remove("iPad")             # 按值删除(删第一个匹配项)
last = products.pop()               # 弹出最后一个元素并返回
products.pop(0)                     # 弹出索引 0 的元素
del products[0]                     # 删除索引 0 的元素
products.clear()                    # 清空列表

# ====== 改 ======
products = ["iPhone", "MacBook", "iPad"]
products[0] = "iPhone 15 Pro"       # 修改索引 0 的元素

# ====== 查 ======
products = ["iPhone", "MacBook", "iPad", "MacBook"]
products.index("MacBook")           # 1(第一次出现的位置)
products.count("MacBook")           # 2(出现次数)
"iPad" in products                  # True(是否包含)

1.3 列表的排序

# 数字排序
prices = [8999, 5999, 399, 14999, 1899]

sorted(prices)              # [399, 1899, 5999, 8999, 14999]  升序,不改变原列表
sorted(prices, reverse=True) # [14999, 8999, 5999, 1899, 399] 降序

prices.sort()               # 原地排序,会改变原列表
prices.sort(reverse=True)   # 原地降序

# 按指定规则排序(用 key 参数)
products = [
    {"name": "iPhone", "price": 8999},
    {"name": "iPad", "price": 4799},
    {"name": "AirPods", "price": 1899},
]

# 按价格排序
by_price = sorted(products, key=lambda p: p["price"])
# → AirPods(1899), iPad(4799), iPhone(8999)

# 按价格降序
by_price_desc = sorted(products, key=lambda p: p["price"], reverse=True)
# → iPhone(8999), iPad(4799), AirPods(1899)

# 按名称长度排序
by_name_len = sorted(products, key=lambda p: len(p["name"]))
# → iPad(4), iPhone(6), AirPods(7)

lambda 是匿名函数的写法,lambda p: p["price"] 等价于:

def get_price(p):
    return p["price"]

1.4 列表的切片

切片不仅能用于字符串,也能用于列表,语法相同:

items = ["a", "b", "c", "d", "e"]

items[1:3]    # ["b", "c"]       取索引 1 到 2
items[:3]     # ["a", "b", "c"]  前 3 个
items[2:]     # ["c", "d", "e"]  从索引 2 到末尾
items[-2:]    # ["d", "e"]       最后 2 个
items[::-1]   # ["e", "d", "c", "b", "a"]  反转

# 实际场景:取列表前 10 条作为"热门商品"
top10 = products[:10]

# 复制列表(切片可以用来复制,避免引用问题)
original = [1, 2, 3]
copy = original[:]    # 独立副本,修改 copy 不影响 original

1.5 嵌套列表

列表的元素可以是任何类型,包括列表本身:

# 二维列表:表格数据
order_items = [
    ["iPhone 15 Pro", 8999, 1],
    ["MacBook Pro 14", 14999, 1],
    ["AirPods Pro 2", 1899, 2],
]

# 取第一行第二列(第一个商品的价格)
order_items[0][1]    # 8999

# 遍历嵌套列表
for item in order_items:
    name = item[0]
    price = item[1]
    qty = item[2]
    print(f"  {name} × {qty} = ¥{price * qty}")

# 计算总价
total = sum(item[1] * item[2] for item in order_items)

在实际的接口测试中,响应数据通常是字典套列表再套字典的多层嵌套结构,需要熟练掌握层层取值。


二、字典 dict 深入

字典是 Python 中另一个核心数据结构,本质是键值对的无序集合。接口返回的 JSON 解析后就是字典。

2.1 字典的基本操作

user = {
    "id": 1,
    "username": "admin",
    "role": "admin"
}

# ====== 查 ======
user["username"]              # "admin"(键不存在会报 KeyError)
user.get("username")          # "admin"(键不存在返回 None,推荐用这个)
user.get("age", 0)            # 0(键不存在时返回默认值 0)

# ====== 增/改 ======
user["email"] = "admin@test.com"  # 新增键值对
user["role"] = "superadmin"       # 修改已有的值

# ====== 删 ======
del user["email"]                  # 删除键值对
email = user.pop("email", None)    # 删除并返回值,键不存在返回 None

# ====== 判断 ======
"username" in user                 # True(判断键是否存在)

2.2 字典的遍历

user = {"id": 1, "username": "admin", "role": "admin"}

# 遍历键
for key in user:
    print(key)           # id, username, role

# 遍历值
for value in user.values():
    print(value)         # 1, admin, admin

# 遍历键值对(最常用)
for key, value in user.items():
    print(f"  {key}: {value}")

2.3 字典合并

# Python 3.9+ 用 | 运算符
base_config = {"timeout": 10, "retry": 3}
env_config = {"base_url": "http://test.com", "timeout": 30}
merged = base_config | env_config
# → {"timeout": 30, "retry": 3, "base_url": "http://test.com"}
# 注意:timeout 被后面的覆盖了

# 通用写法(兼容所有 Python 3 版本)
merged = {**base_config, **env_config}

# update 方法(会修改原字典)
base_config.update(env_config)

2.4 嵌套字典(重点)

接口返回的 JSON 通常是多层嵌套的字典,这是自动化测试中最常处理的数据结构:

# 模拟一个接口响应
response = {
    "code": 200,
    "message": "success",
    "data": {
        "total": 15,
        "page": 1,
        "items": [
            {
                "id": 1,
                "name": "iPhone 15 Pro",
                "price": 8999,
                "specs": {
                    "color": "深空黑",
                    "storage": "256GB"
                }
            },
            {
                "id": 2,
                "name": "iPhone 15",
                "price": 5999,
                "specs": {
                    "color": "蓝色",
                    "storage": "128GB"
                }
            }
        ]
    }
}

# 层层取值
response["code"]                                    # 200
response["data"]["total"]                           # 15
response["data"]["items"]                           # 商品列表
response["data"]["items"][0]                        # 第一个商品
response["data"]["items"][0]["name"]                # "iPhone 15 Pro"
response["data"]["items"][0]["specs"]["color"]      # "深空黑"

# 安全取值(避免 KeyError)
response.get("data", {}).get("items", [])           # 如果 data 不存在返回空字典
response.get("data", {}).get("items", [])[0].get("name", "未知")  # 链式安全取值

2.5 字典推导式

类似列表推导式,字典也有自己的推导式:

products = [
    {"name": "iPhone", "price": 8999},
    {"name": "iPad", "price": 4799},
    {"name": "AirPods", "price": 1899},
]

# 生成 {商品名: 价格} 的映射
price_map = {p["name"]: p["price"] for p in products}
# → {"iPhone": 8999, "iPad": 4799, "AirPods": 1899}

# 筛选价格超过 3000 的
expensive = {p["name"]: p["price"] for p in products if p["price"] > 3000}
# → {"iPhone": 8999, "iPad": 4799}

三、元组 tuple

元组和列表很像,但有一个关键区别:元组不可变,创建后不能增删改。

# 创建元组
point = (100, 200)
rgb_color = (255, 128, 0)

# 访问元素(和列表一样用索引)
point[0]    # 100
point[1]    # 200

# ❌ 不能修改
# point[0] = 999  → 报错 TypeError

# 元组拆包
x, y = point
print(x)    # 100
print(y)    # 200

# 函数返回多个值时,实际返回的就是元组
def get_bounds(products):
    prices = [p["price"] for p in products]
    return min(prices), max(prices)

lowest, highest = get_bounds(products)

什么时候用元组而不是列表:当数据不应该被修改时用元组。比如坐标的 (x, y)、RGB 颜色值 (r, g, b)、数据库查询返回的一行记录等。


四、集合 set

集合是一个无序的、元素不重复的容器,最大的用途是去重集合运算

4.1 基本操作

# 创建集合
categories = {"手机", "笔记本", "平板", "配件"}

# 自动去重
names = ["iPhone", "iPad", "iPhone", "MacBook", "iPad"]
unique_names = set(names)    # {"iPhone", "iPad", "MacBook"}

# 增删
categories.add("智能穿戴")       # 添加
categories.discard("智能穿戴")   # 删除(元素不存在不报错)

4.2 集合运算

# 假设两组用户的权限
admin_permissions = {"read", "write", "delete", "manage"}
user_permissions = {"read", "write"}

# 交集:都有哪些权限
both = admin_permissions & user_permissions    # {"read", "write"}

# 并集:所有权限
all_perms = admin_permissions | user_permissions  # {"read", "write", "delete", "manage"}

# 差集:admin 有但 user 没有的
admin_only = admin_permissions - user_permissions  # {"delete", "manage"}

# 实际场景:找出新增的和删除的商品
old_products = {"iPhone 14", "iPhone 15", "MacBook Pro"}
new_products = {"iPhone 15", "iPhone 16", "MacBook Pro", "iPad Air"}

added = new_products - old_products    # {"iPhone 16", "iPad Air"}  新增的
removed = old_products - new_products  # {"iPhone 14"}             下架的
unchanged = old_products & new_products # {"iPhone 15", "MacBook Pro"}  没变的

五、字符串高级操作

5.1 join 拼接

# 把列表拼接成字符串(和 split 相反)
names = ["iPhone", "iPad", "MacBook"]
result = "、".join(names)       # "iPhone、iPad、MacBook"
result = ", ".join(names)      # "iPhone, iPad, MacBook"
result = "".join(names)        # "iPhoneiPadMacBook"

# 实际场景:拼接 URL 路径
parts = ["api", "v1", "products", "123"]
url = "/".join(parts)          # "api/v1/products/123"

5.2 strip / lstrip / rstrip

text = "  Hello World  "
text.strip()      # "Hello World"   去除两边空格
text.lstrip()     # "Hello World  " 去除左边空格
text.rstrip()     # "  Hello World" 去除右边空格

# 也能去除指定字符
price_str = "¥999元"
price_str.strip("¥元")    # "999"

5.3 zfill 补零

# 给数字前面补零,常用于生成编号
order_id = str(1).zfill(6)     # "000001"
order_id = str(42).zfill(6)    # "000042"
order_id = str(12345).zfill(6) # "012345"

5.4 isdigit / isalpha / isalnum

"12345".isdigit()     # True(全是数字)
"hello".isalpha()     # True(全是字母)
"hello123".isalnum()  # True(全是字母或数字)
"".isspace()          # False
"   ".isspace()       # True(全是空白字符)

# 实际场景:验证用户输入
username = "admin123"
if username.isalnum():
    print("用户名格式正确")
else:
    print("用户名只能包含字母和数字")

5.5 多行字符串与文本处理

# 多行文本处理
log_text = """
[2024-01-15 10:30:01] INFO: 用户 admin 登录成功
[2024-01-15 10:30:05] INFO: 查询商品列表,返回 15 条
[2024-01-15 10:30:10] ERROR: 创建订单失败,购物车为空
[2024-01-15 10:30:15] INFO: 用户 vipuser 登录成功
"""

# 按行分割并去除空行
lines = [line.strip() for line in log_text.strip().split("\n") if line.strip()]

# 筛选 ERROR 日志
errors = [line for line in lines if "ERROR" in line]
print(f"发现 {len(errors)} 条错误日志:")
for err in errors:
    print(f"  {err}")

六、动手实战:订单管理函数

创建新文件 order_functions.py,编写 MallLite 的订单管理模块。

"""
MallLite 电商系统 - 订单管理与数据统计
功能:订单创建、查询、取消、数据统计分析
"""

import json
import os
from datetime import datetime

# ========================================
# 加载数据
# ========================================
data_dir = "data"

def load_json(filename):
    """加载 JSON 数据文件"""
    filepath = os.path.join(data_dir, filename)
    with open(filepath, "r", encoding="utf-8") as f:
        return json.load(f)

products = load_json("products.json")
users = load_json("users.json")

# 复用上一篇的函数
def get_product_by_id(product_id):
    """根据 ID 查询商品"""
    for p in products:
        if p["id"] == product_id:
            return p
    return None

print(f"加载完成:{len(products)} 个商品,{len(users)} 个用户")
print("=" * 60)


# ========================================
# 第一部分:订单管理
# ========================================

# 订单列表(内存中维护)
orders = []
# 订单号计数器
order_counter = 0


def generate_order_no():
    """
    生成订单号:ORD + 日期 + 6 位序号
    例如:ORD20240115-000001
    """
    global order_counter
    order_counter += 1
    date_str = datetime.now().strftime("%Y%m%d")
    seq = str(order_counter).zfill(6)
    return f"ORD{date_str}-{seq}"


def create_order(user_id, cart_items):
    """
    从购物车创建订单

    参数:
        user_id: 用户 ID
        cart_items: 购物车商品列表,每个元素格式:
                    {"product_id": 1, "quantity": 2}

    返回:
        创建结果字典,包含 success、message、order
    """
    # 验证购物车是否为空
    if not cart_items:
        return {"success": False, "message": "购物车为空,无法创建订单", "order": None}

    # 构建订单商品明细
    order_items = []
    total_price = 0

    for cart_item in cart_items:
        product_id = cart_item["product_id"]
        quantity = cart_item["quantity"]

        # 查找商品
        product = get_product_by_id(product_id)
        if product is None:
            return {"success": False, "message": f"商品 ID={product_id} 不存在", "order": None}

        # 检查库存
        if product["stock"] < quantity:
            return {
                "success": False,
                "message": f"{product['name']} 库存不足(需要 {quantity},剩余 {product['stock']})",
                "order": None
            }

        # 构建订单项
        subtotal = product["price"] * quantity
        order_items.append({
            "product_id": product_id,
            "name": product["name"],
            "price": product["price"],
            "quantity": quantity,
            "subtotal": subtotal
        })
        total_price += subtotal

    # 扣减库存
    for cart_item in cart_items:
        product = get_product_by_id(cart_item["product_id"])
        product["stock"] -= cart_item["quantity"]

    # 创建订单
    order_no = generate_order_no()
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    order = {
        "order_no": order_no,
        "user_id": user_id,
        "items": order_items,
        "total_price": total_price,
        "item_count": len(order_items),
        "total_quantity": sum(item["quantity"] for item in order_items),
        "status": "待支付",
        "created_at": now,
        "updated_at": now
    }

    orders.append(order)

    return {"success": True, "message": f"订单 {order_no} 创建成功", "order": order}


def get_order_list(user_id=None, status=None):
    """
    获取订单列表,支持按用户和状态筛选

    参数:
        user_id: 用户 ID(可选,不传则返回所有订单)
        status: 订单状态(可选,如 "待支付"、"已取消")

    返回:
        符合条件的订单列表
    """
    result = orders

    if user_id is not None:
        result = [o for o in result if o["user_id"] == user_id]

    if status is not None:
        result = [o for o in result if o["status"] == status]

    return result


def get_order_by_no(order_no):
    """
    根据订单号查询订单

    参数:
        order_no: 订单号

    返回:
        找到返回订单字典,找不到返回 None
    """
    for order in orders:
        if order["order_no"] == order_no:
            return order
    return None


def cancel_order(order_no):
    """
    取消订单(恢复库存)

    参数:
        order_no: 订单号

    返回:
        操作结果字典
    """
    order = get_order_by_no(order_no)
    if order is None:
        return {"success": False, "message": f"订单 {order_no} 不存在"}

    if order["status"] == "已取消":
        return {"success": False, "message": f"订单 {order_no} 已经是取消状态"}

    if order["status"] == "已完成":
        return {"success": False, "message": f"订单 {order_no} 已完成,无法取消"}

    # 恢复库存
    for item in order["items"]:
        product = get_product_by_id(item["product_id"])
        if product:
            product["stock"] += item["quantity"]

    order["status"] = "已取消"
    order["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    return {"success": True, "message": f"订单 {order_no} 已取消,库存已恢复"}


def pay_order(order_no):
    """
    支付订单

    参数:
        order_no: 订单号

    返回:
        操作结果字典
    """
    order = get_order_by_no(order_no)
    if order is None:
        return {"success": False, "message": f"订单 {order_no} 不存在"}

    if order["status"] != "待支付":
        return {"success": False, "message": f"订单 {order_no} 状态为 {order['status']},无法支付"}

    order["status"] = "已支付"
    order["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    return {"success": True, "message": f"订单 {order_no} 支付成功,金额 ¥{order['total_price']}"}


def print_order(order):
    """美观地打印订单详情"""
    print(f"  订单号:{order['order_no']}")
    print(f"  状态:{order['status']}")
    print(f"  下单时间:{order['created_at']}")
    print(f"  商品明细:")
    for item in order["items"]:
        print(f"    - {item['name']}  ¥{item['price']} × {item['quantity']} = ¥{item['subtotal']}")
    print(f"  合计:{order['total_quantity']} 件,¥{order['total_price']}")


# ========================================
# 测试订单管理
# ========================================
print("\n【订单管理功能】")
print("-" * 60)

# 模拟登录用户
current_user = users[1]  # testuser

print(f"\n当前用户:{current_user['nickname']}(ID={current_user['id']})")

# 订单 1:正常下单
print("\n1. 创建订单 1(正常下单):")
cart1 = [
    {"product_id": 1, "quantity": 1},   # iPhone 15 Pro × 1
    {"product_id": 11, "quantity": 2},   # AirPods Pro 2 × 2
]
result = create_order(current_user["id"], cart1)
print(f"  {result['message']}")
if result["success"]:
    print_order(result["order"])
    order1_no = result["order"]["order_no"]

# 订单 2:另一笔订单
print("\n2. 创建订单 2(购买笔记本):")
cart2 = [
    {"product_id": 5, "quantity": 1},   # MacBook Pro 14 × 1
]
result = create_order(current_user["id"], cart2)
print(f"  {result['message']}")
if result["success"]:
    print_order(result["order"])
    order2_no = result["order"]["order_no"]

# 订单 3:库存不足
print("\n3. 测试库存不足:")
cart3 = [{"product_id": 5, "quantity": 999}]
result = create_order(current_user["id"], cart3)
print(f"  {result['message']}")

# 订单 4:空购物车
print("\n4. 测试空购物车下单:")
result = create_order(current_user["id"], [])
print(f"  {result['message']}")

# 支付订单 1
print("\n5. 支付订单 1:")
result = pay_order(order1_no)
print(f"  {result['message']}")

# 取消订单 2
print("\n6. 取消订单 2:")
result = cancel_order(order2_no)
print(f"  {result['message']}")

# 查看当前用户所有订单
print(f"\n7. {current_user['nickname']} 的所有订单:")
user_orders = get_order_list(user_id=current_user["id"])
for order in user_orders:
    print(f"  - {order['order_no']} | {order['status']} | ¥{order['total_price']}")

# 按状态筛选
print(f"\n8. 筛选已支付的订单:")
paid_orders = get_order_list(status="已支付")
for order in paid_orders:
    print(f"  - {order['order_no']} | ¥{order['total_price']}")

print()


# ========================================
# 第二部分:数据统计分析
# ========================================
print("【数据统计分析】")
print("=" * 60)


# ========================================
# 统计 1:各分类商品数量和平均价格
# ========================================
print("\n统计 1:各分类商品统计")
print("-" * 60)

def analyze_categories(products):
    """
    统计各分类的商品数量、平均价格、最高价、最低价

    参数:
        products: 商品列表

    返回:
        各分类的统计数据字典
    """
    stats = {}

    for p in products:
        cat = p["category"]
        if cat not in stats:
            stats[cat] = {
                "count": 0,
                "total_price": 0,
                "prices": [],
                "names": []
            }
        stats[cat]["count"] += 1
        stats[cat]["total_price"] += p["price"]
        stats[cat]["prices"].append(p["price"])
        stats[cat]["names"].append(p["name"])

    # 计算统计值
    for cat, data in stats.items():
        data["avg_price"] = data["total_price"] / data["count"]
        data["min_price"] = min(data["prices"])
        data["max_price"] = max(data["prices"])
        # 删除临时字段,只保留统计数据
        del data["prices"]

    return stats

category_stats = analyze_categories(products)

# 用格式化对齐输出
print(f"  {'分类':<8} {'数量':>4} {'平均价':>8} {'最低价':>8} {'最高价':>8}")
print(f"  {'—' * 40}")
for cat, data in category_stats.items():
    print(f"  {cat:<8} {data['count']:>4} {data['avg_price']:>8.0f} {data['min_price']:>8} {data['max_price']:>8}")


# ========================================
# 统计 2:价格区间分布
# ========================================
print(f"\n统计 2:价格区间分布")
print("-" * 60)

def analyze_price_distribution(products):
    """
    统计商品在各价格区间的分布

    参数:
        products: 商品列表

    返回:
        各区间的商品数量和占比
    """
    ranges = [
        ("¥0-500", 0, 500),
        ("¥500-2000", 500, 2000),
        ("¥2000-5000", 2000, 5000),
        ("¥5000-10000", 5000, 10000),
        ("¥10000+", 10000, float("inf")),
    ]

    total = len(products)
    distribution = []

    for label, low, high in ranges:
        count = len([p for p in products if low <= p["price"] < high])
        percentage = count / total * 100 if total > 0 else 0
        distribution.append({
            "range": label,
            "count": count,
            "percentage": percentage,
            "names": [p["name"] for p in products if low <= p["price"] < high]
        })

    return distribution

distribution = analyze_price_distribution(products)

for item in distribution:
    bar = "█" * int(item["percentage"] / 5)  # 每 5% 一个方块
    print(f"  {item['range']:<12} {item['count']:>2} 个 ({item['percentage']:>5.1f}%) {bar}")


# ========================================
# 统计 3:库存预警
# ========================================
print(f"\n统计 3:库存预警(低于 80 件)")
print("-" * 60)

def get_stock_alerts(products, threshold=80):
    """
    获取库存预警商品

    参数:
        products: 商品列表
        threshold: 库存阈值

    返回:
        低于阈值的商品列表,按库存从低到高排序
    """
    alerts = [p for p in products if p["stock"] < threshold]
    return sorted(alerts, key=lambda p: p["stock"])

alerts = get_stock_alerts(products, threshold=80)

if alerts:
    for p in alerts:
        level = "🔴 紧急" if p["stock"] < 50 else "🟡 预警"
        print(f"  {level} {p['name']:<20} 仅剩 {p['stock']} 件")
else:
    print("  所有商品库存充足")


# ========================================
# 统计 4:新品与非新品对比
# ========================================
print(f"\n统计 4:新品 vs 非新品")
print("-" * 60)

def compare_new_vs_old(products):
    """
    对比新品和非新品的数量、平均价格、平均评分

    返回:
        包含新品和非新品统计数据的字典
    """
    new_items = [p for p in products if p["is_new"]]
    old_items = [p for p in products if not p["is_new"]]

    def calc_stats(items, label):
        if not items:
            return {"label": label, "count": 0, "avg_price": 0, "avg_rating": 0}
        return {
            "label": label,
            "count": len(items),
            "avg_price": sum(p["price"] for p in items) / len(items),
            "avg_rating": sum(p["rating"] for p in items) / len(items),
        }

    return calc_stats(new_items, "新品"), calc_stats(old_items, "非新品")

new_stats, old_stats = compare_new_vs_old(products)

print(f"  {'类型':<8} {'数量':>4} {'平均价格':>10} {'平均评分':>8}")
print(f"  {'—' * 36}")
for stats in [new_stats, old_stats]:
    print(f"  {stats['label']:<8} {stats['count']:>4} {stats['avg_price']:>10.0f} {stats['avg_rating']:>8.1f}")


# ========================================
# 统计 5:订单统计(如果有订单数据)
# ========================================
print(f"\n统计 5:订单统计")
print("-" * 60)

def analyze_orders(order_list):
    """
    统计订单数据

    参数:
        order_list: 订单列表

    返回:
        订单统计数据字典
    """
    if not order_list:
        return {
            "total_orders": 0,
            "total_revenue": 0,
            "avg_order_price": 0,
            "status_distribution": {},
            "top_products": []
        }

    # 按状态统计
    status_count = {}
    for order in order_list:
        status = order["status"]
        if status not in status_count:
            status_count[status] = 0
        status_count[status] += 1

    # 计算总收入(只统计已支付的订单)
    paid_orders = [o for o in order_list if o["status"] == "已支付"]
    total_revenue = sum(o["total_price"] for o in paid_orders)

    # 统计商品销量
    product_sales = {}
    for order in order_list:
        if order["status"] != "已取消":
            for item in order["items"]:
                name = item["name"]
                if name not in product_sales:
                    product_sales[name] = 0
                product_sales[name] += item["quantity"]

    # 按销量排序取前 5
    top_products = sorted(product_sales.items(), key=lambda x: x[1], reverse=True)[:5]

    return {
        "total_orders": len(order_list),
        "total_revenue": total_revenue,
        "avg_order_price": total_revenue / len(paid_orders) if paid_orders else 0,
        "status_distribution": status_count,
        "top_products": top_products
    }

order_stats = analyze_orders(orders)

print(f"  总订单数:{order_stats['total_orders']}")
print(f"  已支付收入:¥{order_stats['total_revenue']}")
print(f"  平均客单价:¥{order_stats['avg_order_price']:.0f}")
print(f"  状态分布:")
for status, count in order_stats["status_distribution"].items():
    print(f"    {status}{count} 笔")
if order_stats["top_products"]:
    print(f"  销量 Top 商品:")
    for name, qty in order_stats["top_products"]:
        print(f"    {name}{qty} 件")


# ========================================
# 统计 6:用字典推导式做快速统计
# ========================================
print(f"\n统计 6:字典推导式快速统计")
print("-" * 60)

# 商品名 → 价格 映射
price_map = {p["name"]: p["price"] for p in products}
print(f"  商品价格映射(前 5 个):")
for i, (name, price) in enumerate(price_map.items()):
    if i >= 5:
        break
    print(f"    {name} → ¥{price}")

# 各分类最低价商品
cheapest_by_category = {}
for p in products:
    cat = p["category"]
    if cat not in cheapest_by_category or p["price"] < cheapest_by_category[cat]["price"]:
        cheapest_by_category[cat] = p

print(f"\n  各分类最低价商品:")
for cat, p in cheapest_by_category.items():
    print(f"    {cat}{p['name']}(¥{p['price']})")


# ========================================
# 总结
# ========================================
print(f"\n{'=' * 60}")
print("本次编写了以下函数和统计模块:")
print("=" * 60)

functions = [
    ("generate_order_no()", "生成订单号"),
    ("create_order(user_id, cart_items)", "创建订单"),
    ("get_order_list(user_id, status)", "获取订单列表(支持筛选)"),
    ("get_order_by_no(order_no)", "根据订单号查询"),
    ("cancel_order(order_no)", "取消订单(恢复库存)"),
    ("pay_order(order_no)", "支付订单"),
    ("print_order(order)", "打印订单详情"),
    ("analyze_categories(products)", "各分类商品统计"),
    ("analyze_price_distribution(products)", "价格区间分布"),
    ("get_stock_alerts(products, threshold)", "库存预警"),
    ("compare_new_vs_old(products)", "新品 vs 非新品对比"),
    ("analyze_orders(order_list)", "订单统计"),
]

for func_name, desc in functions:
    print(f"  {func_name:<48} # {desc}")

print(f"\n数据结构使用统计:")
usage = [
    ("列表 list", "商品列表、订单列表、购物车、统计结果"),
    ("字典 dict", "商品信息、订单信息、配置、统计映射"),
    ("集合 set", "分类去重、权限对比"),
    ("元组 tuple", "函数多返回值、价格区间定义"),
    ("列表推导式", "筛选商品、提取字段、数据转换"),
    ("字典推导式", "价格映射、最低价统计"),
    ("嵌套结构", "订单包含商品列表,每个商品又是一个字典"),
]
for struct, usage_desc in usage:
    print(f"  {struct:<16}{usage_desc}")

运行脚本

python order_functions.py

完整输出

加载完成:15 个商品,3 个用户
============================================================

【订单管理功能】
------------------------------------------------------------

当前用户:测试用户(ID=2)

1. 创建订单 1(正常下单):
  订单 ORD20240115-000001 创建成功
  订单号:ORD20240115-000001
  状态:待支付
  下单时间:2024-01-15 10:30:01
  商品明细:
    - iPhone 15 Pro  ¥8999 × 1 = ¥8999
    - AirPods Pro 2  ¥1899 × 2 = ¥3798
  合计:3 件,¥12797

2. 创建订单 2(购买笔记本):
  订单 ORD20240115-000002 创建成功
  订单号:ORD20240115-000002
  状态:待支付
  下单时间:2024-01-15 10:30:02
  商品明细:
    - MacBook Pro 14  ¥14999 × 1 = ¥14999
  合计:1 件,¥14999

3. 测试库存不足:
  MacBook Pro 14 库存不足(需要 999,剩余 49)

4. 测试空购物车下单:
  购物车为空,无法创建订单

5. 支付订单 1:
  订单 ORD20240115-000001 支付成功,金额 ¥12797

6. 取消订单 2:
  订单 ORD20240115-000002 已取消,库存已恢复

7. 测试用户的所有订单:
  - ORD20240115-000001 | 已支付 | ¥12797
  - ORD20240115-000002 | 已取消 | ¥14999

8. 筛选已支付的订单:
  - ORD20240115-000001 | ¥12797

【数据统计分析】
============================================================

统计 1:各分类商品统计
------------------------------------------------------------
  分类       数量     平均价     最低价     最高价
  ————————————————————————————————————————
  手机          4     6749     4999     8999
  笔记本        3    11332     8999    14999
  平板          3     5932     3999     8999
  配件          5     1219      199     3299

统计 2:价格区间分布
------------------------------------------------------------
  ¥0-500        3 个 ( 20.0%) ████
  ¥500-2000     1 个 (  6.7%) █
  ¥2000-5000    3 个 ( 20.0%) ████
  ¥5000-10000   5 个 ( 33.3%) ██████
  ¥10000+       3 个 ( 20.0%) ████

统计 3:库存预警(低于 80 件)
------------------------------------------------------------
  🔴 紧急 华为 MateBook X Pro   仅剩 45 件
  🔴 紧急 MacBook Pro 14        仅剩 49 件
  🟡 预警 华为 Mate 60 Pro      仅剩 79 件
  🟡 预警 iPad Pro 12.9         仅剩 69 件

统计 4:新品 vs 非新品
------------------------------------------------------------
  类型       数量     平均价格   平均评分
  ————————————————————————————————————
  新品          6       6549      4.7
  非新品        9       4343      4.5

统计 5:订单统计
------------------------------------------------------------
  总订单数:2
  已支付收入:¥12797
  平均客单价:¥12797
  状态分布:
    已支付:1 笔
    已取消:1 笔
  销量 Top 商品:
    iPhone 15 Pro:1 件
    AirPods Pro 2:2 件
    MacBook Pro 14:1 件

统计 6:字典推导式快速统计
------------------------------------------------------------
  商品价格映射(前 5 个):
    iPhone 15 Pro → ¥8999
    iPhone 15 → ¥5999
    华为 Mate 60 Pro → ¥6999
    小米 14 Pro → ¥4999
    MacBook Pro 14 → ¥14999

  各分类最低价商品:
    手机:小米 14 Pro(¥4999)
    笔记本:华为 MateBook X Pro(¥8999)
    平板:华为 MatePad Pro(¥3999)
    配件:USB-C 转接头(¥199)

============================================================
本次编写了以下函数和统计模块:
============================================================
  generate_order_no()                                  # 生成订单号
  create_order(user_id, cart_items)                    # 创建订单
  get_order_list(user_id, status)                      # 获取订单列表(支持筛选)
  get_order_by_no(order_no)                            # 根据订单号查询
  cancel_order(order_no)                               # 取消订单(恢复库存)
  pay_order(order_no)                                  # 支付订单
  print_order(order)                                   # 打印订单详情
  analyze_categories(products)                         # 各分类商品统计
  analyze_price_distribution(products)                 # 价格区间分布
  get_stock_alerts(products, threshold)                # 库存预警
  compare_new_vs_old(products)                         # 新品 vs 非新品对比
  analyze_orders(order_list)                           # 订单统计

数据结构使用统计:
  列表 list        → 商品列表、订单列表、购物车、统计结果
  字典 dict        → 商品信息、订单信息、配置、统计映射
  集合 set         → 分类去重、权限对比
  元组 tuple       → 函数多返回值、价格区间定义
  列表推导式       → 筛选商品、提取字段、数据转换
  字典推导式       → 价格映射、最低价统计
  嵌套结构         → 订单包含商品列表,每个商品又是一个字典

七、本篇用到的数据结构知识总结

数据结构特点本篇的应用场景
列表 list有序、可变、可重复商品列表、订单列表、购物车、统计结果列表
字典 dict键值对、无序、键不重复商品信息、订单信息、统计数据、价格映射
元组 tuple有序、不可变价格区间定义 (low, high)、函数返回多个值
集合 set无序、不重复分类去重、新旧商品对比
列表推导式一行代码完成筛选和转换筛选低价商品、提取商品名列表
字典推导式一行代码生成键值映射商品名到价格的映射
嵌套结构字典套列表再套字典订单结构(订单 → 商品列表 → 每个商品的详情)

八、课后练习

练习 1:添加订单搜索函数

def search_orders(keyword):
    """
    搜索订单(在订单号和商品名中搜索关键词)

    参数:
        keyword: 搜索关键词

    返回:
        匹配的订单列表
    """
    results = []
    for order in orders:
        # 在订单号中搜索
        if keyword in order["order_no"]:
            results.append(order)
            continue
        # 在商品名中搜索
        for item in order["items"]:
            if keyword in item["name"]:
                results.append(order)
                break
    return results

# 测试
results = search_orders("iPhone")
print(f"搜索 'iPhone',找到 {len(results)} 笔订单")

练习 2:添加商品销量排行函数

def get_sales_ranking(order_list, top_n=5):
    """
    根据已支付订单统计商品销量排行

    参数:
        order_list: 订单列表
        top_n: 返回前 N 名

    返回:
        销量排行列表,每项包含商品名和销量
    """
    sales = {}
    for order in order_list:
        if order["status"] in ["已支付", "待支付"]:
            for item in order["items"]:
                name = item["name"]
                sales[name] = sales.get(name, 0) + item["quantity"]

    ranking = sorted(sales.items(), key=lambda x: x[1], reverse=True)
    return ranking[:top_n]

ranking = get_sales_ranking(orders, top_n=3)
print("销量排行:")
for i, (name, qty) in enumerate(ranking, 1):
    print(f"  第 {i} 名:{name}{qty} 件)")

练习 3:添加用户消费统计函数

def get_user_spending(order_list, user_id):
    """
    统计某个用户的消费情况

    返回:
        包含订单数、总消费、已支付消费、平均客单价的字典
    """
    user_orders = [o for o in order_list if o["user_id"] == user_id]
    paid_orders = [o for o in user_orders if o["status"] == "已支付"]

    return {
        "total_orders": len(user_orders),
        "total_spending": sum(o["total_price"] for o in paid_orders),
        "avg_order_price": sum(o["total_price"] for o in paid_orders) / len(paid_orders) if paid_orders else 0,
        "paid_orders": len(paid_orders),
        "cancelled_orders": len([o for o in user_orders if o["status"] == "已取消"])
    }

stats = get_user_spending(orders, user_id=2)
print(f"用户消费统计:{stats}")

九、今日成果总结

今天完成了什么:

  • 深入学习了列表的排序、切片、嵌套操作
  • 深入学习了字典的嵌套取值、合并、推导式
  • 理解了元组和集合的适用场景
  • 学习了字符串的高级操作(join、strip、zfill 等)
  • 编写了完整的订单管理模块(创建、查询、支付、取消)
  • 编写了 6 个数据统计分析函数
  • 综合运用了所有数据结构知识

当前项目结构:

mall-lite/
├── data/
│   ├── products.json     ← 15 个商品
│   ├── categories.json   ← 4 个分类
│   └── users.json        ← 3 个用户
├── generate_data.py      ← P01:数据生成脚本
├── mall_functions.py     ← P02:核心业务函数(查询、搜索、分页、购物车)
├── order_functions.py    ← P03:订单管理与数据统计(本篇)
├── hello.py
└── venv/

十、下篇预告

P04 - 文件操作与异常处理

下一篇将学习 JSON、YAML、CSV、Excel 文件的读写操作,以及 try/except 异常处理机制。然后用这些知识为 MallLite 编写一个完整的数据存储层(DataStore),把之前散落各处的数据操作统一封装,为 P05 搭建 FastAPI 后端做好准备。


系列导航

序号标题状态
P01Python 环境搭建与第一行代码✅ 已发布
P02流程控制与函数✅ 已发布
P03数据结构与常用操作✅ 本文
P04文件操作与异常处理下一篇
P05面向对象与模块待更新
P06自动化测试常用技巧待更新
01Web UI 自动化项目总览与环境搭建待更新
02框架基础层搭建待更新
Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐