bigywater.tech
Django · 15

URL Resolver 匹配顺序

请求进来之后,Django 怎么在 urlpatterns 里找到视图?顺序有多重要?模式多了会不会慢?这篇不靠文档,用真实的路由表逐条试,并量了不同长度路由表下的匹配耗时。

第一个胜出
按 urlpatterns 顺序,第一个匹配的模式生效,后面同名的永远到不了
POST 无斜杠
APPEND_SLASH 下 DEBUG=False 时是 301(请求体丢失),DEBUG=True 才抛 RuntimeError
712 µs
5000 个 path() 的路由表里匹配最后一个的耗时(第一个仅 4.4 µs)

顺序与转换器

实测 · 本机执行输出
/a/5/                                        → a_int          kwargs={'pk': 5}
/a/new/                                      → a_new          kwargs={}
/a/abc/                                      → a_str          kwargs={'s': 'abc'}
/a/5                                         → Resolver404
/first/                                      → first          kwargs={}
/b/my-slug_1/                                → b_slug         kwargs={'s': 'my-slug_1'}
/b/has space/                                → Resolver404
/c/x/y/z                                     → c_path         kwargs={'p': 'x/y/z'}
/c/x/y/z/                                    → c_path         kwargs={'p': 'x/y/z/'}
/u/123e4567-e89b-12d3-a456-426614174000/     → u_uuid         kwargs={'u': UUID('123e4567-e89b-12d3-a456-426614174000')}
/u/not-a-uuid/                               → Resolver404
/r/2026/                                     → r_year         kwargs={'year': '2026'}
/r/26/                                       → Resolver404
/nothing/                                    → Resolver404

同一个 URL 可能符合多个模式,顺序决定结果:把具体的(a/new/)放在通用的(a/<str:s>/)之前。转换器既限定匹配范围,也决定 kwargs 里的类型。

ResolverMatch 与命名空间

实测 · 本机执行输出
func      = user_detail
route     = api/v1/users/<int:pk>/
url_name  = user
namespace = 'v1'
app_names = ['api']
kwargs    = {'pk': 7}
view_name = v1:user

resolve() 返回的 ResolverMatch 记录了完整的 route(所有 include 前缀累加后的样子)、url_name、namespace、app_names。

结尾斜杠

实测 · 本机执行输出
GET /a/new/        → 200 
GET /a/new         → 301 Location: /a/new/
GET /a/new?x=1     → 301 Location: /a/new/?x=1
GET /first         → 301 Location: /first/
POST /a/new       → 301
  DEBUG=True 时异常原文: RuntimeError: You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to testser

CommonMiddleware 在响应阶段发现 404 且 APPEND_SLASH=True、补一个斜杠就能匹配时,返回 301。GET 没问题;POST 的请求体在重定向中丢失——DEBUG=True 时 Django 用一个 RuntimeError 提醒你,DEBUG=False 时不提醒。

匹配的代价

实测 · 本机执行输出
  N=10    第 1 个      4.3 µs   中间      4.9 µs   最后      5.4 µs   不匹配      3.8 µs
  N=100   第 1 个      4.4 µs   中间      9.7 µs   最后     15.8 µs   不匹配     13.7 µs
  N=1000  第 1 个      4.4 µs   中间     60.5 µs   最后    118.1 µs   不匹配    113.2 µs
  N=5000  第 1 个      4.4 µs   中间    350.2 µs   最后    711.7 µs   不匹配    674.0 µs

每种规模量最快的一次(5 轮取最小值)。第一个模式的耗时和总数无关(≈4 µs),越靠后越慢,404 与匹配最后一个几乎一样——因为都要把整张表扫完。对大多数项目这不是瓶颈,但路由表上千条、并且热点路由排在后面时值得调整。(这里是 path() 且没有 include() 分层;分层会缩短每次要扫描的范围,本文没有测。)

分步演示:路由匹配

URL Resolver
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。计时是进程内调用 django.urls.resolve(不含网络与中间件);取 5 轮里最小的一轮
    • 没有验证:include() 分层对匹配耗时的影响、re_path 的性能与 path 的对比、i18n_patterns / 国际化 URL、404 页面(DEBUG=True)列出已尝试模式的行为
    京ICP备2026067502号-1京公网安备11010502063487号