Django url 路由匹配过程详解


Posted in Python onJanuary 22, 2021

1 Django 如何处理一个请求

当一个用户请求Django 站点的一个页面,下面是Django 系统决定执行哪个Python 代码使用的算法:

Django 确定使用根 URLconf 模块。通常,这是 ROOT_URLCONF 设置的值(即 settings 中的 ROOT_URLCONF),但如果传入 HttpRequest 对象拥有 urlconf 属性(通过中间件设置),它的值将被用来代替 ROOT_URLCONF 设置。可以在 django/core/handlers/base.py 发现该逻辑。

class BaseHandler:
  ...
  def _get_response(self, request):
    ...
    if hasattr(request, 'urlconf'):
      urlconf = request.urlconf
      set_urlconf(urlconf)
      resolver = get_resolver(urlconf)
    else:
      resolver = get_resolver()

Django 加载该 Python 模块并寻找可用的 urlpatterns 。它是 django.urls.path() 和(或) django.urls.re_path() 实例的序列(sequence)。其实就是我们写的 url.py

Django 会按顺序遍历每个 URL 模式,然后会在所请求的URL匹配到第一个模式后停止,并与 path_info 匹配。这个是路由匹配的关键,相关逻辑均在django/urls/resolvers.py。其中有几个比较重要的概念,如RegexPattern、RoutePattern、URLPattern、URLResolver。其中URLResolver有嵌套的逻辑,下文详述。

一旦有 URL 匹配成功,Django 导入并调用相关的视图,这个视图是一个Python 函数(或基于类的视图 class-based view )。匹配成功会返回一个ResolverMatch对象。

如果没有 URL 被匹配,或者匹配过程中出现了异常,Django 会调用一个适当的错误处理视图。

本文详述 2、3,即 urlpatterns 相关概念和路由匹配的过程。

2 URL 配置文件

在 Django 2 之后通常会使用 path/re_path 来设置路由,还要一个比较特殊的方法 include 。

  • path: 用于普通路径
  • re_path:用于正则路径
  • include: 将一个子 url 配置文件导入

如下示例:

urlpatterns = [
  path('index/', views.index), # 普通路径
  re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
  path("app01/", include("app01.urls")),
]

上面的配置文件,设置了3条 urlpattern,分别是普通路径 index/ 与 视图函数 views.index,正则路径 ^articles/([0-9]{4})/$ 与视图函数 views.articles 绑定。app01/ 和app01.urls 绑定,app01.urls 不是一个视图函数,而是一个子模块的 urlpatterns。
可以看到 urlpattern 可以把一个 url 和视图函数绑定,也可以和一个子 urlpattern 进行绑定。

2.1 path、re_path

设置路由的几个函数均定义在 django/urls/conf.py 中。

def include(arg, namespace=None):
  ...
  return (urlconf_module, app_name, namespace)


def _path(route, view, kwargs=None, name=None, Pattern=None):
  if isinstance(view, (list, tuple)):
    # For include(...) processing.
    pattern = Pattern(route, is_endpoint=False)
    urlconf_module, app_name, namespace = view
    return URLResolver(
      pattern,
      urlconf_module,
      kwargs,
      app_name=app_name,
      namespace=namespace,
    )
  elif callable(view):
    pattern = Pattern(route, name=name, is_endpoint=True)
    return URLPattern(pattern, view, kwargs, name)
  else:
    raise TypeError('view must be a callable or a list/tuple in the case of include().')


path = partial(_path, Pattern=RoutePattern)
re_path = partial(_path, Pattern=RegexPattern)

首先先来看下 path 和 re_path,这两个函数分别被 functools 下面的 partial 封装了一下。partial 的作用简单来说就是将一个函数的某些参数给固定住,返回一个新的函数。详细文档可以查看partial 文档。
这样就不难理解 path 和 re_path,他们就是就是绑定了不同的 Pattern 参数的 _path 函数。进一步查看 _path 内部的逻辑,

第一个分支 如果绑定的是一个 list或者tuple,使用 URLResolver 去解析,其实此时就是使用了 include 来定义 urlpattern。
另外一种情况如果绑定的 view 是可以调用的,那就使用 URLPattern 去解析。URLPattern 中的 pattern 参数就是根据是采用 path/re_path 方法分别对应 RoutePattern/RegexPattern。

2.2 include

def include(arg, namespace=None):
  ...
  if isinstance(urlconf_module, str):
    urlconf_module = import_module(urlconf_module)
  patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module)
  app_name = getattr(urlconf_module, 'app_name', app_name)
  ...
  return (urlconf_module, app_name, namespace)

include 方法所做的工作就是通过 import_module 将定义的 url 模块导入。返回一个由子 urlconf 模块、app_name、命名空间 namespace 组成的元组。回到刚刚上面的 _path 中第一个分支。将这个元组里面参数代入 URLResolver 并返回。

3 URLPattern 与 URLResolver

3.1 URLPattern

上面提到如果url定义中绑定是一个可以直接调用的view。那就是使用URLPattern直接去解析。

class URLPattern:
  def __init__(self, pattern, callback, default_args=None, name=None):
    # 需要匹配的 urlpattern,这里根据是path还是re_path 分别是 RoutePattern或RegexPattern的实例
    self.pattern = pattern
    self.callback = callback # the view
    self.default_args = default_args or {}
    self.name = name
  ...
  def resolve(self, path):
    调用 RoutePattern 或 RegexPattern 的实例中的 match 方法进行匹配(注意这里不是 re 模块里面的 match)
    match = self.pattern.match(path)
    if match:
      new_path, args, kwargs = match
      # Pass any extra_kwargs as **kwargs.
      kwargs.update(self.default_args)
      # 匹配成功返回 `ResolverMatch`
      return ResolverMatch(self.callback, args, kwargs, self.pattern.name, route=str(self.pattern))
  ...

URLPattern 初始化时其中的 pattern 就是根据是使用 path/re_path 分别对应RoutePattern或RegexPattern。其实就是指定匹配的模式是普通路由还是正则的路由。

3.2 URLResolver

URLResolver 源码中比较核心的是 resolve 函数,就是传入一个 path,进行匹配。

class URLResolver:
  def resolve(self, path):
    path = str(path) # path may be a reverse_lazy object
    tried = []
    # 匹配 path
    match = self.pattern.match(path)
    if match:
      new_path, args, kwargs = match
      # 如果匹配成功,则继续匹配它的url_patterns
      for pattern in self.url_patterns:
        try:
          # 这个pattern可能是URLPattern,也可能是URLResolver;如果是URLPattern,匹配成功则返回ResolverMatch;如果是URLResolver,则会递归调用下去。
          sub_match = pattern.resolve(new_path)
        ...
        else:
          if sub_match:
            ...
            # 匹配成功返回ResolverMatch
            return ResolverMatch(
              sub_match.func,
              sub_match_args,
              sub_match_dict,
              sub_match.url_name,
              [self.app_name] + sub_match.app_names,
              [self.namespace] + sub_match.namespaces,
              self._join_route(current_route, sub_match.route),
            )
          tried.append([pattern])
      raise Resolver404({'tried': tried, 'path': new_path})
    raise Resolver404({'path': path})

URLResolver 比较关键的逻辑在 循环匹配 pattern 过程,如果 pattern是URLPattern匹配成功直接返回ResolverMatch,如果是另一个URLResolver,则实现了递归调用。

Django url 路由匹配过程详解

Django 就是通过这个 URLResolver 实现了多级 URL 配置。

4 总结

Django 路由匹配的有几个比较核心的概念 path/re_path/include、RegexPattern/RoutePattern、URLPattern/URLResolver。
首先用 partial 封装 _path,绑定了一个 pattern 匹配模式(RegexPattern/RoutePattern),后面多次用到了这个 pattern。然后就是根据 view 是元组还是可调用视图函数,分别使用URLResolver和URLPattern去解析,这两个类解析之后都会返回给ResolverMatch,由它去回调匹配成功后的结果(view和args等)。

本文从全局的角度大致说明了Django路由的匹配流程,后续将从细节部分说明其中的一些关键点。

到此这篇关于Django url 路由匹配过程详解的文章就介绍到这了,更多相关Django url 路由匹配内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python中apply函数的用法实例教程
Jul 31 Python
Python入门篇之对象类型
Oct 17 Python
浅谈python中requests模块导入的问题
May 18 Python
Python SMTP发送邮件遇到的一些问题及解决办法
Oct 24 Python
Python 数值区间处理_对interval 库的快速入门详解
Nov 16 Python
Python3.0中普通方法、类方法和静态方法的比较
May 03 Python
python实现几种归一化方法(Normalization Method)
Jul 31 Python
基于python判断目录或者文件代码实例
Nov 29 Python
Python动态声明变量赋值代码实例
Dec 30 Python
关于windows下Tensorflow和pytorch安装教程
Feb 04 Python
PyCharm2020.1.2社区版安装,配置及使用教程详解(Windows)
Aug 07 Python
python程序的组织结构详解
Dec 06 Python
浅析pandas随机排列与随机抽样
Jan 22 #Python
python 合并多个excel中同名的sheet
Jan 22 #Python
Python读取pdf表格写入excel的方法
Jan 22 #Python
python 基于UDP协议套接字通信的实现
Jan 22 #Python
详解matplotlib中pyplot和面向对象两种绘图模式之间的关系
Jan 22 #Python
Jmeter调用Python脚本实现参数互相传递的实现
Jan 22 #Python
Python实现王者荣耀自动刷金币的完整步骤
Jan 22 #Python
You might like
第十五节--Zend引擎的发展
2006/11/16 PHP
php表单转换textarea换行符的方法
2010/09/10 PHP
thinkPHP5.0框架简单配置作用域的方法
2017/03/17 PHP
defer属性导致引用JQuery的页面报“浏览器无法打开网站xxx,操作被中止”错误的解决方法
2010/04/27 Javascript
js实现双向链表互联网机顶盒实战应用实现
2011/10/28 Javascript
jQuery 自定义函数写法分享
2012/03/30 Javascript
利用JavaScript实现新闻滚动效果(实例代码)
2013/11/27 Javascript
javascript中对Attr(dom中属性)的操作示例讲解
2013/12/02 Javascript
jQuery动态改变图片显示大小(修改版)的实现思路及代码
2013/12/24 Javascript
jquery 无限级下拉菜单的简单实现代码
2014/02/21 Javascript
js实现字符串的16进制编码不加密
2014/04/25 Javascript
jquery delay()介绍及使用指南
2014/09/02 Javascript
node.js中的fs.existsSync方法使用说明
2014/12/17 Javascript
jQuery动画出现连续触发、滞后反复执行的解决方法
2015/01/28 Javascript
jQuery+ajax实现无刷新级联菜单示例
2015/05/21 Javascript
Adapter适配器模式在JavaScript设计模式编程中的运用分析
2016/05/18 Javascript
jquery实现放大镜简洁代码(推荐)
2017/06/08 jQuery
浅谈vue自定义全局组件并通过全局方法 Vue.use() 使用该组件
2017/12/07 Javascript
[01:30]DOTA2上海特锦赛现场采访 Loda倾情献唱
2016/03/25 DOTA
python计算对角线有理函数插值的方法
2015/05/07 Python
Python 创建空的list,以及append用法讲解
2018/05/04 Python
Python os.rename() 重命名目录和文件的示例
2018/10/25 Python
Python缓存技术实现过程详解
2019/09/25 Python
Python算法中的时间复杂度问题
2019/11/19 Python
Python绘图实现显示中文
2019/12/04 Python
Python集合操作方法详解
2020/02/09 Python
python算的上脚本语言吗
2020/06/22 Python
Python使用struct处理二进制(pack和unpack用法)
2020/11/12 Python
加拿大领先家居家具网上购物:Aosom.ca
2020/05/27 全球购物
GWebs公司笔试题
2012/05/04 面试题
电气专业推荐信范文
2013/11/18 职场文书
服装区域经理岗位职责
2015/04/10 职场文书
爱护环境卫生倡议书
2015/04/29 职场文书
小型婚礼主持词
2015/06/30 职场文书
python爬虫框架feapde的使用简介
2021/04/20 Python
win10更新失败无限重启解决方法
2022/04/19 数码科技