【Python】Django url 路由匹配过程

Django url 路由匹配过程

Guixian发布于 2 分钟前

1 Django 如何处理一个请求

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

  1. 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()

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

  3. Django 会按顺序遍历每个 URL 模式,然后会在所请求的URL匹配到第一个模式后停止,并与 path_info 匹配。这个是路由匹配的关键,相关逻辑均在django/urls/resolvers.py。其中有几个比较重要的概念,如RegexPatternRoutePatternURLPatternURLResolver。其中URLResolver有嵌套的逻辑,下文详述。
  4. 一旦有 URL 匹配成功,Django 导入并调用相关的视图,这个视图是一个Python 函数(或基于类的视图 class-based view )。匹配成功会返回一个ResolverMatch对象。
  5. 如果没有 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,则实现了递归调用。
【Python】Django url 路由匹配过程

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

总结

Django 路由匹配的有几个比较核心的概念 path/re_path/include、RegexPattern/RoutePattern、URLPattern/URLResolver。
首先用 partial 封装 _path,绑定了一个 pattern 匹配模式(RegexPattern/RoutePattern),后面多次用到了这个 pattern。然后就是根据 view 是元组还是可调用视图函数,分别使用URLResolver和URLPattern去解析,这两个类解析之后都会返回给ResolverMatch,由它去回调匹配成功后的结果(view和args等)。
本文从全局的角度大致说明了Django路由的匹配流程,后续将从细节部分说明其中的一些关键点。

pythondjango

阅读 7发布于 2 分钟前

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

Guixian

1 声望

0 粉丝

0 条评论

得票时间

avatar

Guixian

1 声望

0 粉丝

宣传栏

1 Django 如何处理一个请求

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

  1. 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()

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

  3. Django 会按顺序遍历每个 URL 模式,然后会在所请求的URL匹配到第一个模式后停止,并与 path_info 匹配。这个是路由匹配的关键,相关逻辑均在django/urls/resolvers.py。其中有几个比较重要的概念,如RegexPatternRoutePatternURLPatternURLResolver。其中URLResolver有嵌套的逻辑,下文详述。
  4. 一旦有 URL 匹配成功,Django 导入并调用相关的视图,这个视图是一个Python 函数(或基于类的视图 class-based view )。匹配成功会返回一个ResolverMatch对象。
  5. 如果没有 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,则实现了递归调用。
【Python】Django url 路由匹配过程

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

总结

Django 路由匹配的有几个比较核心的概念 path/re_path/include、RegexPattern/RoutePattern、URLPattern/URLResolver。
首先用 partial 封装 _path,绑定了一个 pattern 匹配模式(RegexPattern/RoutePattern),后面多次用到了这个 pattern。然后就是根据 view 是元组还是可调用视图函数,分别使用URLResolver和URLPattern去解析,这两个类解析之后都会返回给ResolverMatch,由它去回调匹配成功后的结果(view和args等)。
本文从全局的角度大致说明了Django路由的匹配流程,后续将从细节部分说明其中的一些关键点。

以上是 【Python】Django url 路由匹配过程 的全部内容, 来源链接: utcz.com/a/106378.html

回到顶部