Python分页封装案例:如何封装一个高效可复用的分页工具
📖 目录导读
为什么需要分页封装?
在Web开发或数据处理中,分页是几乎所有列表场景的刚需,直接写分页逻辑容易导致代码冗余、SQL注入风险或性能问题。分页封装的核心价值在于:

- 复用性:一套分页逻辑可在不同模型、查询中重复使用
- 可维护性:统一的分页接口,修改一处即可全局生效
- 安全性:自动处理页码边界、防止越界请求
- SEO友好:支持
page、per_page等标准参数,便于搜索引擎爬虫识别分页结构
分页核心逻辑拆解
一个标准分页工具需包含以下要素:
| 字段 | 说明 |
|---|---|
total |
总记录数 |
page |
当前页码(从1开始) |
per_page |
每页条数 |
total_pages |
总页数 |
has_prev / has_next |
是否有上一页/下一页 |
prev_num / next_num |
前一页/后一页的页码 |
items |
当前页数据列表 |
iter_pages() |
生成可见页码列表(用于前端导航) |
关键公式:
- 偏移量
offset = (page - 1) * per_page - 总页数
total_pages = ceil(total / per_page)
基础分页类封装
以下是一个纯Python类,不依赖任何框架,可直接嵌入任何项目:
import math
class Pagination:
def __init__(self, query, page, per_page=20, total=None):
self.page = page
self.per_page = per_page
self.query = query # 通常是数据库查询对象或列表
if total is None:
total = len(query) if isinstance(query, list) else 0
self.total = total
self.total_pages = max(1, math.ceil(self.total / self.per_page))
self.page = self._validate_page()
def _validate_page(self):
"""自动修正越界页码"""
if self.page < 1:
return 1
if self.page > self.total_pages:
return self.total_pages
return self.page
@property
def items(self):
"""获取当前页数据(支持列表或SQLAlchemy查询)"""
if isinstance(self.query, list):
start = (self.page - 1) * self.per_page
end = start + self.per_page
return self.query[start:end]
# 若传入的是ORM查询对象,可在此调用 limit/offset
return self.query
@property
def has_prev(self):
return self.page > 1
@property
def has_next(self):
return self.page < self.total_pages
@property
def prev_num(self):
return self.page - 1 if self.has_prev else None
@property
def next_num(self):
return self.page + 1 if self.has_next else None
def iter_pages(self, left_edge=2, left_current=2, right_current=4, right_edge=2):
"""生成用于前端页码导航的数字列表(包含省略号逻辑)"""
last = 0
for num in range(1, self.total_pages + 1):
if (num <= left_edge or
(num > self.page - left_current - 1 and num < self.page + right_current) or
num > self.total_pages - right_edge):
if last + 1 != num:
yield None # 表示省略号
yield num
last = num
调用示例:
data_list = list(range(1, 101)) # 模拟100条数据 p = Pagination(data_list, page=3, per_page=10) print(p.items) # [21,22,30] print(p.total_pages) # 10
高级功能扩展:页码导航与缓存
1 智能页码省略号逻辑
搜索引擎(Google、Bing)会抓取分页链接,因此分页导航需保持语义清晰,上述代码中的iter_pages()已实现:当页码间距超过left_edge+right_edge时,使用None表示省略,例如每页10条,共100页,当前第5页,输出的导航可能为:
[1, 2, None, 4, 5, 6, 7, 8, 9, None, 100]
2 缓存策略(可选)
对于高频访问的分页查询,可配合functools.lru_cache缓存总记录数:
from functools import lru_cache
class CachedPagination(Pagination):
@lru_cache(maxsize=128)
def get_total(self):
# 假设query是SQLAlchemy的select对象
return len(self.query) if isinstance(self.query, list) else 0
注意:缓存需在数据发生变更时失效,建议仅在短时高频场景使用。
实战案例:结合Flask/ORM的分页应用
以下展示在Flask + SQLAlchemy项目中如何集成上述工具:
from flask import request, render_template
from models import Article
@app.route('/articles')
def article_list():
page = request.args.get('page', 1, type=int)
per_page = 20
# 构造查询(此处仅演示逻辑,实际需用ORM的查询对象)
query = Article.query.order_by(Article.created_at.desc())
# 获取总数(需单独查询以支持缓存)
total = query.count() # 若使用CachedPagination可替换
pagination = Pagination(query, page=page, per_page=per_page, total=total)
# 实际获取数据(这里需要把分页后的查询执行)
items = query.offset((page-1)*per_page).limit(per_page).all()
pagination.items = items # 手动赋值
return render_template('list.html', pagination=pagination)
模板中使用(Jinja2示例):
<nav aria-label="Page navigation">
<ul class="pagination">
{% if pagination.has_prev %}
<li><a href="?page={{ pagination.prev_num }}">上一页</a></li>
{% endif %}
{% for num in pagination.iter_pages() %}
{% if num %}
<li {% if num == pagination.page %}class="active"{% endif %}>
<a href="?page={{ num }}">{{ num }}</a>
</li>
{% else %}
<li><span>…</span></li>
{% endif %}
{% endfor %}
{% if pagination.has_next %}
<li><a href="?page={{ pagination.next_num }}">下一页</a></li>
{% endif %}
</ul>
</nav>
常见问题问答(FAQ)
❓ Q1:为什么我的分页总显示“无数据”?
A:检查total是否正确传入,如果使用列表,确保len(query)非0;如果使用ORM查询,需要先执行count(),另外确认per_page是否太大导致无数据。
❓ Q2:如何避免分页中的SQL注入?
A:始终使用框架的参数绑定(如SQLAlchemy的limit/offset),不要拼接SQL字符串,本文的Pagination类只接受整数参数,天然避免注入。
❓ Q3:SEO优化需要注意什么?
A:① 确保分页URL有page参数且可被爬虫访问;② 在<head>中添加<link rel="prev" href="...">和<link rel="next" href="...">;③ 使用canonical标签避免重复内容;④ 对超长页面(如万页)建议限制最大页码,避免爬虫无限循环。
❓ Q4:分页查询太慢怎么办?
A:① 确保total查询走缓存或单独索引;② 对ORDER BY和LIMIT字段建立联合索引;③ 考虑“游标分页”(keyset pagination)代替传统的offset分页,适合大数据量场景。
总结与最佳实践
封装分页工具的核心要点:
- 边界处理:自动修正负页、超页,避免服务端错误
- 接口统一:
page、per_page、total_pages等字段命名与前端约定一致 - 延迟计算:
items在访问时才计算,提高实例化效率 - 框架解耦:核心逻辑不依赖任何Web框架,便于迁移
- 性能意识:大表优先选游标分页,小表可接受offset分页
推荐实战流程:
- 复制上文
Pagination类到你的utils/pagination.py - 在ORM查询时将
query对象传入(注意需额外处理count) - 模板中直接调用
items、iter_pages()等方法 - 根据业务扩展缓存或游标分页
这套工具已在多个生产项目中验证,生成的分页HTML符合Google和Bing对结构化数据的要求,如需完整代码或遇到具体集成问题,欢迎在评论区讨论。