paginator 初始化时 queryset 不能是 list,必须传未求值的 queryset,否则失去数据库分页能力、导致内存溢出;page_obj.object_list 是切片后结果,不可链式查询;get_page() 比 page() 更安全,自动兜底;模板中应使用 has_other_pages() 判断首尾页显示。

Paginator 初始化时 queryset 不能是 list
直接把 list 传给 Paginator 看似能跑,但会失去数据库分页能力,所有数据提前加载进内存——列表一过万条就卡死或 OOM。
必须传入未求值的 QuerySet,比如 Article.objects.filter(published=True),而不是 list(Article.objects.filter(...))。
常见错误现象:TypeError: object of type 'list' has no len() 或页面响应极慢、内存暴涨。
page_obj.object_list 是当前页数据,不是原始 QuerySet
page_obj.object_list 是已经切片后的结果(QuerySet 或 list),不能再链式调用 .filter() 或 .order_by();想加筛选得在初始化 Paginator 前做完。
使用场景:分页后做前端渲染,或对当前页做轻量处理(如补全关联字段);别试图在 object_list 上再查库。
性能影响:如果误在 object_list 上调用 .count(),Django 会强制执行 SQL,而 Paginator 已经通过 paginator.count 缓存了总数。
get_page() 比 page() 更安全,自动处理非法页码
page() 遇到非数字、负数、超范围页码直接抛 EmptyPage 或 PageNotAnInteger 异常,不捕获就会 500;get_page() 则统一返回第一页(1)或最后一页,适合用户直接输 URL 参数的场景。
实操建议:
- 用户可控的分页入口(如 ?page=xxx)一律用
paginator.get_page(request.GET.get('page')) - 后台任务或 API 内部逻辑需精确控制时,才用
page()+ try/except - 注意
get_page()返回None的情况极少,但若传入None或空字符串,它仍会尝试转成 int,失败则回退到第一页
模板里用 page_obj.has_other_pages() 判断要不要显示「首页/末页」按钮
很多人只靠 page_obj.has_previous 和 page_obj.has_next 控制左右箭头,但漏掉首尾页——尤其当总页数很多时,用户没法快速跳转。page_obj.has_other_pages() 在总页数 ≤ 1 时返回 False,否则为 True,是判断是否需要渲染「第 1 页」「最后一页」的最简依据。
容易踩的坑:
- 硬写
{% if page_obj.paginator.num_pages > 3 %}—— 逻辑冗余且和实际 UI 需求脱节 - 用
page_obj.number == 1判断是否显示「首页」,但没考虑只有一页时不该显示 - 忘记给「首页」链接加
?page=1,导致点完还在当前页
立即学习“Python免费学习笔记(深入)”;
分页本身不难,难的是在 queryset 生命周期、异常边界、模板状态之间保持一致——尤其是get_page() 的“静默兜底”行为,看着省事,但掩盖了参数校验缺失的问题。真要稳,得从 request 入口就明确页码来源和默认值。










