核心概念
分页的本质是从大量的数据中,只截取当前页面需要显示的一小部分数据。

这个过程通常需要两个关键信息:
- 当前页码: 用户想看哪一页?我们通常从第 1 页开始计数。
- 每页大小: 每页显示多少条数据?
通过这两个信息,我们就可以计算出需要从原始列表中提取数据的起始索引和结束索引。
实现步骤
假设我们有一个原始数据列表 all_items,以及当前页码 page_number 和每页大小 page_size。
步骤 1: 计算起始索引

起始索引的计算公式非常直接:
start_index = (page_number - 1) * page_size
- 为什么是
page_number - 1? 因为页码是从 1 开始的,而列表的索引是从 0 开始的。- 第 1 页:
(1 - 1) * page_size = 0(从索引 0 开始) - 第 2 页:
(2 - 1) * page_size = page_size(从索引page_size开始) - 第 3 页:
(3 - 1) * page_size = 2 * page_size(从索引2 * page_size开始)
- 第 1 页:
步骤 2: 计算结束索引
结束索引的计算公式:

end_index = start_index + page_size
步骤 3: 切片列表
利用 Python 的列表切片功能,从 all_items 中提取出 start_index 到 end_index 之间的元素。
page_items = all_items[start_index:end_index]
步骤 4: 处理边界情况
如果用户请求的页码超出了总页数,我们应该返回一个空列表,而不是报错。
- 总页数:
total_pages = (len(all_items) + page_size - 1) // page_size(这是一个向上取整的技巧) - 检查:
page_number > total_pages,则page_items应为空列表。
代码示例
下面我们用 Python 来实现一个完整的分页函数。
示例 1: 基础分页函数
这个函数接收列表、页码和每页大小,返回分页后的数据。
def paginate_list(items: list, page_number: int, page_size: int) -> list:
"""
对列表进行分页。
:param items: 原始数据列表
:param page_number: 当前页码 (从1开始)
:param page_size: 每页显示的数量
:return: 分页后的子列表
"""
if not items or page_size <= 0:
return []
# 计算总页数
total_items = len(items)
total_pages = (total_items + page_size - 1) // page_size # 向上取整
# 处理页码超出范围的情况
if page_number < 1 or page_number > total_pages:
return []
# 计算切片的起始和结束索引
start_index = (page_number - 1) * page_size
end_index = start_index + page_size
# 返回分页后的数据
return items[start_index:end_index]
# --- 使用示例 ---
all_products = [f"Product {i}" for i in range(1, 101)] # 模拟一个有100个商品的列表
current_page = 3
items_per_page = 10
paginated_data = paginate_list(all_products, current_page, items_per_page)
print(f"当前页码: {current_page}")
print(f"每页大小: {items_per_page}")
print(f"分页后的数据 (共 {len(paginated_data)} 条):")
print(paginated_data)
# 输出:
# 当前页码: 3
# 每页大小: 10
# 分页后的数据 (共 10 条):
# ['Product 21', 'Product 22', 'Product 23', 'Product 24', 'Product 25', 'Product 26', 'Product 27', 'Product 28', 'Product 29', 'Product 30']
示例 2: 返回更丰富的分页信息 (推荐)
在实际应用中,我们通常不只返回分页数据,还需要返回一些元信息,比如总条数、总页数、是否有下一页等,以便前端构建分页导航栏。
我们可以返回一个字典,包含所有这些信息。
def get_paginated_response(items: list, page_number: int, page_size: int) -> dict:
"""
获取包含分页信息的响应。
:param items: 原始数据列表
:param page_number: 当前页码 (从1开始)
:param page_size: 每页显示的数量
:return: 包含分页数据和元信息的字典
"""
if not items or page_size <= 0:
return {
"items": [],
"total_items": 0,
"total_pages": 0,
"current_page": page_number,
"page_size": page_size,
"has_next": False,
"has_previous": False
}
total_items = len(items)
total_pages = (total_items + page_size - 1) // page_size
# 处理页码超出范围的情况
if page_number < 1 or page_number > total_pages:
return {
"items": [],
"total_items": total_items,
"total_pages": total_pages,
"current_page": page_number,
"page_size": page_size,
"has_next": False,
"has_previous": False
}
start_index = (page_number - 1) * page_size
end_index = start_index + page_size
page_items = items[start_index:end_index]
return {
"items": page_items,
"total_items": total_items,
"total_pages": total_pages,
"current_page": page_number,
"page_size": page_size,
"has_next": page_number < total_pages,
"has_previous": page_number > 1
}
# --- 使用示例 ---
all_products = [f"Product {i}" for i in range(1, 101)]
current_page = 2
items_per_page = 15
response = get_paginated_response(all_products, current_page, items_per_page)
import json
print(json.dumps(response, indent=2, ensure_ascii=False))
# 输出:
{
"items": [
"Product 16",
"Product 17",
"Product 18",
"Product 19",
"Product 20",
"Product 21",
"Product 22",
"Product 23",
"Product 24",
"Product 25",
"Product 26",
"Product 27",
"Product 28",
"Product 29",
"Product 30"
],
"total_items": 100,
"total_pages": 7,
"current_page": 2,
"page_size": 15,
"has_next": true,
"has_previous": true
}
高级场景与最佳实践
1 数据库分页
如果你的数据量非常大(例如数百万条),一次性加载所有数据到内存中再分页是非常低效且不可行的,这时,应该在数据库查询层面进行分页。
SQL (以 PostgreSQL 为例)
使用 LIMIT 和 OFFSET 关键字:
-- 获取第 2 页,每页 10 条数据 SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 10; -- (2-1)*10 = 10
LIMIT 10: 限制返回 10 条记录。OFFSET 10: 跳过前面的 10 条记录。
ORM (以 Django ORM 为例)
Django 的 ORM 封装了数据库分页,非常方便。
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
queryset = Product.objects.all() # 假设这是你的查询集
page_number = request.GET.get('page') # 从URL获取页码
paginator = Paginator(queryset, 10) # 每页10条
try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
# 如果页码不是整数,则显示第一页
page_obj = paginator.page(1)
except EmptyPage:
# 如果页码超出范围,则显示最后一页
page_obj = paginator.page(paginator.num_pages)
# 在模板中遍历
# for product in page_obj.object_list:
# print(product.name)
# 获取分页信息
print(page_obj.has_next()) # 是否有下一页
print(page_obj.has_previous()) # 是否有上一页
print(page_obj.number) # 当前页码
print(paginator.count) # 总条数
2 性能优化
- 避免
OFFSET过大: 在LIMIT ... OFFSET ...查询中,当OFFSET值非常大时,数据库仍然需要扫描并跳过前面的所有行,这会导致性能急剧下降,对于深度分页,可以考虑基于游标分页 或键集分页。 - 游标分页: 它不使用
OFFSET,而是使用上一页最后一条记录的 ID (或唯一键) 来定位下一页的起始位置。SELECT * FROM products WHERE id > last_seen_id ORDER BY id LIMIT 10,这种方法性能非常稳定,不随页码增加而变慢,但无法方便地跳转到指定页码。
3 用户友好的分页组件
前端通常会使用现成的分页组件库(如 Bootstrap Pagination, Ant Design Pagination, Element UI Pagination 等),这些组件会接收你提供的 total_pages, current_page, has_next, has_previous 等信息,自动渲染出美观且功能完整的页码导航、上一页/下一页按钮等。
| 场景 | 方法 | 关键点 |
|---|---|---|
| 内存中的小列表 | 使用 Python 列表切片 list[start:end] |
计算好 start_index 和 end_index,处理边界情况。 |
| 数据库中的大数据集 | 使用 LIMIT 和 OFFSET 或 ORM 的分页方法 |
务必在数据库层面分页,避免内存溢出。 |
| 需要丰富元信息 | 返回一个包含 items, total_pages, has_next 等的字典 |
方便前端构建导航栏。 |
| 深度分页 (性能敏感) | 考虑使用游标分页 替代 OFFSET |
避免 OFFSET 值过大导致的性能问题。 |
希望这份详细的解释能帮助你完全掌握列表分页的实现方法!
