- 准备工作:安装 Tornado 并创建一个基本的应用结构。
- 模拟数据:创建一个模拟的数据源。
- 后端分页逻辑:编写 Tornado 处理器,实现分页查询和返回。
- 前端模板:创建一个 HTML 模板来展示分页数据和导航链接。
- 运行与测试:启动应用并查看效果。
准备工作
确保你已经安装了 Tornado,如果没有,可以通过 pip 安装:

pip install tornado
项目结构如下:
tornado_pagination/
├── main.py # Tornado 应用入口
└── templates/
└── index.html # 前端页面模板
模拟数据
为了演示,我们不需要连接真实的数据库,在 main.py 中,我们创建一个包含 100 个条目的列表作为我们的数据源。
# main.py
# 模拟一个包含100个条目的数据源
# 每个条目是一个字典,包含 id 和 name
ALL_DATA = [{"id": i, "name": f"Item {i}"} for i in range(1, 101)]
# 每页显示多少条数据
ITEMS_PER_PAGE = 10
后端分页逻辑
这是实现分页的核心部分,我们将创建一个 Tornado RequestHandler 来处理分页请求。
分页逻辑要点:

- 从 URL 的查询参数中获取当前页码
page。page不存在或无效,则默认为第 1 页。 - 根据每页显示的数量
ITEMS_PER_PAGE计算出数据的起始索引和结束索引。 - 使用切片从总数据
ALL_DATA中提取当前页的数据。 - 计算总页数,用于生成分页导航。
- 将当前页数据、当前页码、总页数等信息渲染到模板中。
下面是完整的 main.py 代码:
# main.py
import tornado.ioloop
import tornado.web
import tornado.escape
# 模拟一个包含100个条目的数据源
ALL_DATA = [{"id": i, "name": f"Item {i}"} for i in range(1, 101)]
# 每页显示多少条数据
ITEMS_PER_PAGE = 10
class MainHandler(tornado.web.RequestHandler):
def get(self):
# 1. 获取页码参数,默认为1
try:
page = int(self.get_argument("page", 1))
except ValueError:
page = 1
# 确保页码是正整数
if page < 1:
page = 1
# 2. 计算切片的起始和结束索引
start_index = (page - 1) * ITEMS_PER_PAGE
end_index = start_index + ITEMS_PER_PAGE
# 3. 获取当前页的数据
page_data = ALL_DATA[start_index:end_index]
# 4. 计算总页数
total_items = len(ALL_DATA)
total_pages = (total_items + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE
# 5. 将数据传递给模板
self.render(
"index.html",
page_data=page_data,
current_page=page,
total_pages=total_pages,
items_per_page=ITEMS_PER_PAGE
)
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], template_path="templates")
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Server is running on http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
代码解释:
self.get_argument("page", 1): 从 URL 的查询参数中获取page的值,访问http://localhost:8888/?page=3,page的值就是3,如果参数不存在,则使用默认值1。total_pages = (total_items + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE: 这是一个计算总页数的常用技巧,可以正确处理不能被整除的情况,避免了浮点数运算。self.render(...): 将变量传递给 HTML 模板进行渲染。
前端模板
在 templates/index.html 中,我们将展示数据和生成分页导航链接。
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">Tornado Pagination Example</title>
<style>
body { font-family: sans-serif; margin: 2em; }
table { border-collapse: collapse; width: 50%; }
th, td { border: 1px solid #dddddd; text-align: left; padding: 8px; }
thead { background-color: #f2f2f2; }
.pagination { margin-top: 20px; }
.pagination a, .pagination span {
padding: 8px 16px;
text-decoration: none;
border: 1px solid #ddd;
color: #007bff;
}
.pagination a:hover { background-color: #ddd; }
.pagination .current-page {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.pagination .disabled {
color: #ccc;
pointer-events: none;
border-color: #eee;
}
</style>
</head>
<body>
<h1>Item List</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for item in page_data %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
</tr>
{% end %}
</tbody>
</table>
<div class="pagination">
<!-- 上一页链接 -->
{% if current_page > 1 %}
<a href="/?page={{ current_page - 1 }}">« Previous</a>
{% else %}
<span class="disabled">« Previous</span>
{% end %}
<!-- 页码链接 -->
{% for i in range(1, total_pages + 1) %}
{% if i == current_page %}
<span class="current-page">{{ i }}</span>
{% else %}
<a href="/?page={{ i }}">{{ i }}</a>
{% end %}
{% end %}
<!-- 下一页链接 -->
{% if current_page < total_pages %}
<a href="/?page={{ current_page + 1 }}">Next »</a>
{% else %}
<span class="disabled">Next »</span>
{% end %}
</div>
</body>
</html>
模板语法解释:

{% for item in page_data %} ... {% end %}: Tornado 的模板循环语法,用于遍历page_data。{{ item.id }}: Tornado 的模板变量插入语法,用于显示变量值。{% if current_page > 1 %} ... {% else %} ... {% end %}: 条件判断,我们用它来决定是否显示“上一页”链接,以及是否禁用它。href="/?page={{ i }}": 动态生成指向不同页码的链接。
运行与测试
-
确保你的文件结构正确。
-
打开终端,进入
tornado_pagination目录。 -
运行
main.py:python main.py
-
打开你的浏览器,访问
http://localhost:8888。
你应该能看到第一页的数据(1-10条),以及底部的分页导航,点击 "Next »" 或页码 "2",URL 会变成 http://localhost:8888/?page=2,页面会显示第2条数据(11-20条)。
总结与扩展
这个例子提供了一个标准的、易于理解的分页实现,在实际项目中,你可以根据需要进行扩展:
-
URL 友好化:可以使用
tornado.web.urlspec的正则表达式来美化 URL,/page/3而不是/?page=3。# 在 make_app 中 (r"/page/(\d+)", MainHandler),
然后在
get方法中通过self.request.path_args[0]获取页码。 -
ORM 集成:如果你的数据来自数据库(如 SQLAlchemy, Peewee),逻辑是相通的,你只需要将
ALL_DATA[start_index:end_index]替换为数据库查询,例如使用offset()和limit()。# 伪代码,以 SQLAlchemy 为例 page_data = session.query(MyModel).offset(start_index).limit(ITEMS_PER_PAGE).all() total_items = session.query(MyModel).count()
-
AJAX 分页:为了提升用户体验,可以使用 JavaScript (如 Fetch API) 来异步获取数据,然后动态更新页面内容,而无需刷新整个页面,后端可以只返回 JSON 数据,而不是完整的 HTML。
-
更复杂的分页组件:当数据量非常大时(例如成千上万页),可以只显示当前页附近的几个页码,而不是所有页码,以避免导航栏过长。
