Django2.0源码剖析——urls

问题

  • 当我们拿到一个路径的时候,是怎么找到对应的view函数的
  • 为什么可以include其它的urls
  • 为什么urls.py里面需要一个叫urlpatterns的列表,并且里面是一个个的path()或re_path()函数调用
  • 路径pattern开头要不要写反斜杠/,结尾要不要写反斜杠/
  • 路径pattern里要不要写^和$
  • 为什么包含admin.site.urls不需要include

看了下面的剖析并结合Django源码,就可以解答上面这些问题了。

剖析

urls是怎么进行匹配的呢?
其实就是一颗树,DFS(深度优先查找)匹配。

主要函数和类:

  • RegexPattern # 对re的封装,返回新的path、 args、 kwargs
  • URLResolver # 可以理解为非叶子节点,遇到则递归下去
  • URLPattern # 可以理解为叶子节点,匹配则结束
  • ResolverMatch # 匹配对象,可通过func, args, kwargs = ResolverMatch对象来获取对应view函数和参数
  • path/re_path # 根据情况返回URLPattern或URLResolver
  • include

URLResolver和URLPattern都有resolve()方法, 这是递归的关键。

下面是对源码的抽离和精简,方便演示匹配原理。
有2个文件: urls/resolvers.py和urls/conf.py

urls/resolvers.py文件

import re
from importlib import import_module


class Resolver404(Exception):
    pass


class URLResolver:
    def __init__(self, pattern, urlconf_name):
        self.pattern = pattern
        self.urlconf_name = urlconf_name

    def resolve(self, path):
        match = self.pattern.match(path)
        if match:
            new_path, args, kwargs = match
            for pattern in self.urlconf_module.urlpatterns:
                try:
                    sub_match = pattern.resolve(new_path)
                except Resolver404 as e:
                    pass
                else:
                    if sub_match:
                        return ResolverMatch(
                            sub_match.func,
                            sub_match.args,
                            sub_match.kwargs,
                        )
            raise Resolver404({'path': new_path})
        raise Resolver404({'path': path})

    @property
    def urlconf_module(self):
        if isinstance(self.urlconf_name, str):
            return import_module(self.urlconf_name)
        else:
            return self.urlconf_name


class URLPattern:
    def __init__(self, pattern, callback):
        self.pattern = pattern
        self.callback = callback  # the view

    def resolve(self, path):
        match = self.pattern.match(path)
        if match:
            _, args, kwargs = match
            return ResolverMatch(self.callback, args, kwargs)


class ResolverMatch:
    def __init__(self, func, args, kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def __getitem__(self, index):
        return (self.func, self.args, self.kwargs)[index]

    def __repr__(self):
        return "ResolverMatch(func=%s, args=%s, kwargs=%s)" % (self.func, self.args, self.kwargs)


class RegexPattern:
    def __init__(self, regex):
        self.regex = re.compile(regex)

    def match(self, path):
        match = self.regex.search(path)
        if match:
            kwargs = match.groupdict()
            args = () if kwargs else match.groups()
            return path[match.end():], args, kwargs  # 这里path[match.end():]返回后面未匹配的部分。比如'^myapp/'匹配'myapp/bar/', 就变成了'bar/'
        return None

urls/conf.py文件

from importlib import import_module
from types import ModuleType
from .resolvers import RegexPattern, URLResolver, URLPattern


def re_path(route, view):
    if isinstance(view, ModuleType):
        # For include(...) processing.
        pattern = RegexPattern(route)
        urlconf_module = view
        return URLResolver(
            pattern,
            urlconf_module,
        )

    elif callable(view):
        pattern = RegexPattern(route)
        return URLPattern(pattern, view)
    else:
        raise TypeError('blah blah blah')


def include(urlconf_module):
    return import_module(urlconf_module)

ok, 我们的urls模块写好了。下面使用并验证。
有3个文件: urls1.py和urls2.py和test_urls.py

urls1.py

from urls.conf import re_path, include


def foo_list_view(request):
    pass


def foo_view(request, user_id):
    pass


urlpatterns = [
    re_path('^foo/$', foo_list_view),
    re_path('^foo/(?P<user_id>\d+)/$', foo_view),
    re_path('^myapp/', include('urls2')),  # 这里用了include
]

urls2.py

from urls.conf import re_path


def bar_view(request):
    pass


urlpatterns = [
    re_path('^bar/$', bar_view),
]

test_urls.py

from urls.resolvers import URLResolver, RegexPattern
urlconf = 'urls1'
r = URLResolver(RegexPattern(r'^/'), urlconf)  # 树根
print(r.resolve('/foo/'))
print(r.resolve('/foo/1234/'))
print(r.resolve('/myapp/bar/'))

输出

ResolverMatch(func=<function foo_list_view at 0x10b2a0f28>, args=(), kwargs={})
ResolverMatch(func=<function foo_view at 0x10b299158>, args=(), kwargs={'user_id': '1234'})
ResolverMatch(func=<function bar_view at 0x10b299268>, args=(), kwargs={})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值