Python分页封装案例如何封装分页工具

wen python案例 27

Python分页封装案例:如何封装一个高效可复用的分页工具

📖 目录导读

  1. 为什么需要分页封装?
  2. 分页核心逻辑拆解
  3. 基础分页类封装
  4. 高级功能扩展:页码导航与缓存
  5. 实战案例:结合Flask/ORM的分页应用
  6. 常见问题问答(FAQ)
  7. 总结与最佳实践

为什么需要分页封装?

在Web开发或数据处理中,分页是几乎所有列表场景的刚需,直接写分页逻辑容易导致代码冗余、SQL注入风险或性能问题。分页封装的核心价值在于:

Python分页封装案例如何封装分页工具

  • 复用性:一套分页逻辑可在不同模型、查询中重复使用
  • 可维护性:统一的分页接口,修改一处即可全局生效
  • 安全性:自动处理页码边界、防止越界请求
  • SEO友好:支持pageper_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 BYLIMIT字段建立联合索引;③ 考虑“游标分页”(keyset pagination)代替传统的offset分页,适合大数据量场景。


总结与最佳实践

封装分页工具的核心要点:

  1. 边界处理:自动修正负页、超页,避免服务端错误
  2. 接口统一pageper_pagetotal_pages等字段命名与前端约定一致
  3. 延迟计算items在访问时才计算,提高实例化效率
  4. 框架解耦:核心逻辑不依赖任何Web框架,便于迁移
  5. 性能意识:大表优先选游标分页,小表可接受offset分页

推荐实战流程

  1. 复制上文Pagination类到你的utils/pagination.py
  2. 在ORM查询时将query对象传入(注意需额外处理count
  3. 模板中直接调用itemsiter_pages()等方法
  4. 根据业务扩展缓存或游标分页

这套工具已在多个生产项目中验证,生成的分页HTML符合Google和Bing对结构化数据的要求,如需完整代码或遇到具体集成问题,欢迎在评论区讨论。

抱歉,评论功能暂时关闭!