Django源码阅读:路由(三) 您所在的位置:网站首页 python源码阅读神器 Django源码阅读:路由(三)

Django源码阅读:路由(三)

2023-03-30 10:33| 来源: 网络整理| 查看: 265

通过前一篇文章我们已经知道,一个请求进到服务后,会先使用初始正则匹配请求path,如果满足初始正则后,再遍历xxx.urls中的urlpatterns进行挨个匹配。这篇文章我们就接着看urlpatterns中的内容。

urlpatterns

只要做过Django项目,那么你对urlpatterns应该就不会陌生,无论在项目中的根路由文件,还是在每一个app中的url.py文件,都必须存在一个urlpatterns变量,并且变量的类型是一个list。在路由文件中,我们常这样写:

# xxx/urls.py from django.urls import path, re_path, include urlpatterns = [ path("login1/", views.login), path("app01/", include("app01.urls")), ]

urlpatterns中每一个元素,都是一个path(...),我们接着往下看:

path# django/urls/conf.py path = partial(_path, Pattern=RoutePattern) re_path = partial(_path, Pattern=RegexPattern)

path是个偏函数对象,也就是说,它实际上就是_path函数:

# django/urls/conf.py 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().')

我们先不着急看里面的逻辑代码,先看返回的结果,return URLResolver(...)、URLPattern(...)。所以看到这里我们可以知道,urlpatterns里面存的是什么内容了:

# 我们看到的是: urlpatterns = [ path(...), path(...) ] # 实际真正的是: urlpatterns = [ URLPattern(...), URLResolver(...) ]

再回过来看_path(...)函数,至于是URLPattern(...)还是URLResolver(...),取绝于_path中的第二个参数view。

如果view是一个可遍历的对象,例如list、tuple,那么_path返回的就是URLResolver(...)如果view是一个可调用的对象,例如一个func,那么_path返回的就是URLPattern(...)

URLResolver在上一篇文章中讲过了,URLPattern稍后再讲。在此之前,我们先了解下经常和path一起使用另一个函数:include。

include

前面提到,path的第二个参数可以是列表或元组,其实,include函数就是生成这个列表或元组的快捷方式。

下面我们通过源码来总结下include一些用法,为了便于阅读,删除了部分逻辑:

# django/urls/conf.py def include(arg, namespace=None): app_name = None if isinstance(arg, tuple): # Callable returning a namespace hint. urlconf_module, app_name = arg else: # No namespace hint - use manually provided namespace. urlconf_module = arg 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) # 此处注意:namespace参数存在时,app_name参数也必须存在 if namespace and not app_name: raise ImproperlyConfigured("...") namespace = namespace or app_name # ... return (urlconf_module, app_name, namespace)用法1和用法2,也是最常用的,第一个参数直接传app.urls或app.urls.urlpatterns:# 用法1 path("app01/", include("app01.urls")) path("app01/", include(app01.urls)) # 用法2 path("app01/", include("app01.urls.urlpatterns")) path("app01/", include(app01.urls.urlpatterns))

需要注意的是,如果是用法1,app01.urls文件中必须包含urlpatterns变量;如果include中传入的不是字符串,那么需要在文件中导入对应的模块或变量。

用法3和用法4,是在用法1和用法2的基础上加上app_name参数:# 用法3 path("app01/", include(("app01.urls", "app01"))) path("app01/", include((app01.urls, "app01"))) # 用法4 path("app01/", include(("app01.urls.urlpatterns", "app01"))) path("app01/", include((app01.urls.urlpatterns, "app01")))用法5、用法6和用法7,是在用法1、用法3和用法4的基础上加上namespace参数:# 用法5 path("app01/", include("app01.urls", namespace="app01")) path("app01/", include(app01.urls, namespace="app01")) # 用法6 path("app01/", include(("app01.urls", "app01"), namespace="app01")) path("app01/", include((app01.urls, "app01"), namespace="app01")) # 用法7 path("app01/", include(("app01.urls.urlpatterns", "app01"), namespace="app01")) path("app01/", include((app01.urls.urlpatterns, "app01"), namespace="app01"))

需要注意的是,如果是用法5,app01.urls文件中必须包含app_name变量

虽然上面写了7条用法,但总结下来只需要注意3点:

第一个参数可以传字符串,也可以传元组(必须是元组),例如:"app01.urls"、("app01.urls", "app01")第一个参数中路由,可以是路由文件、也可以是文件内的urlpatterns变量,例如:"app01.urls"、"app01.urls.urlpatterns"第二个参数namespace存在时,也必须存在app_name,无论是手动传入,还是在路由文件中定义,都必须可以找到app_name

接下来,我们我们就来了解下URLPattern类,同时它也是路径解析逻辑五大类之一。

类三:URLPattern

从上面可以知道,URLPattern是和URLResolver同级别的类,都发挥着匹配请求路径的作用。不同的是,URLResolver是进行大方向的匹配,在大方向确定后,再使用URLPattern进行精准的匹配,查到可执行的view。

URLPattern的匹配逻辑如下:

# django/urls/resolvers.py class URLPattern: # ... def resolve(self, path): match = self.pattern.match(path) if match: new_path, args, kwargs = match # Pass any extra_kwargs as **kwargs. kwargs.update(self.default_args) return ResolverMatch(self.callback, args, kwargs, self.pattern.name, route=str(self.pattern))

注意,这个方法的命名def resolve()是很有讲究的,必须和URLResolver中的匹配方法是一个名字,这样的话,在URLResolver.resolve中遍历路由时才会形成递归。

通过path()的源码可以了解到,self.patten其实就是RoutePattern、RegexPattern的实例,是扮演真正执行匹配的角色,返回的match是匹配结果,但这个结果并没有直接返回,而是通过ResolverMatch又进行包装了一层,以类的形式返回。

同时,ResolverMatch也是路径解析逻辑五大类之一。

类四:ResolverMatch

先贴上ResolverMatch的源码:

# django/urls/resolvers.py class ResolverMatch: def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None, route=None): self.func = func self.args = args self.kwargs = kwargs self.url_name = url_name self.route = route # If a URLRegexResolver doesn't have a namespace or app_name, it passes # in an empty value. self.app_names = [x for x in app_names if x] if app_names else [] self.app_name = ':'.join(self.app_names) self.namespaces = [x for x in namespaces if x] if namespaces else [] self.namespace = ':'.join(self.namespaces) if not hasattr(func, '__name__'): # A class-based view self._func_path = func.__class__.__module__ + '.' + func.__class__.__name__ else: # A function-based view self._func_path = func.__module__ + '.' + func.__name__ view_path = url_name or self._func_path self.view_name = ':'.join(self.namespaces + [view_path])

这个类里面没多少逻辑了,仅是把一些用到的结果组合了下,为了便于理解,下面把各结果的原始值来源总结了下:

self.func,来源于path中的位置参数viewself.args、self.kwargs,来源于RegexPattern正则匹配路由时,在路由中匹配到的参数值self.url_name,来源于path的关键词参数name,如果没有的话,则会以self.func自动生成一个self.route,来源于path中的位置参数route类五:RoutePattern

前面我们已经说了,路径解析用到了五个类,分类是URLResolver、URLPattern、RegexPattern、RoutePattern、ResolverMatch,其中RoutePattern,就是最后一个类,这里我不打算详情介绍了,因为它里面的逻辑和RegexPattern相似,只是多了一步把route转换成正则的步骤。

总结

为了便于理解,我画了一张图把这五个类串了起来作为Django源码阅读:路由篇的总结



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有