Python探索之URL Dispatcher实例详解

URL dispatcher简单点理解就是根据URL,将请求分发到相应的方法中去处理,它是对URL和View的一个映射,它的实现其实也很简单,就是一个正则匹配的过程,事先定义好正则表达式和该正则表达式对应的view方法,如果请求的URL符合这个正则表达式,那么就分发这个请求到这个view方法中。

有了这个base,我们先抛出几个问题,提前思考一下:

这个映射定义在哪里?当映射很多时,如果有效的组织?

URL中的参数怎么获取,怎么传给view方法?

如何在view或者是template中反解出URL?

好,先来看一个简单的例子:

from django.conf.urls import patterns, url, include
urlpatterns = patterns('',
  url(r'^articles/2003/$', 'news.views.special_case_2003'),
  url(r'^articles/(\d{4})/$', 'news.views.year_archive'),
)
urlpatterns += patterns('',
  url(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive'),
  url(r'model/', include('model_test.urls')),
)

这段代码就是一个URL Dispatcher的例子,它在一个单独的python模块定义,Django中管这个模块叫做URLconf,其实,就是通过python代码方式实现的配置文件,在这个配置中定义了URL路径和对应的处理方法之间的映射。在Django中,是通过“树”的结构来管理URLconf之间的关系的,在Django中的主配置文件中,有一个叫做ROOT_URL_CONF的配置项,就是用来指定根URLconf,从根URLconf开始,逐条进行匹配,直到找到匹配项为止,这就是我们上面提到的第一个问题的答案,下面还会再仔细剖析。

在上面例子中,我们可以看到有3个方法:patterns, url, include。url方法构建了一个URL到View方法的映射关系对象,patterns将这些映射关系对象组织成为一个python的列表,那include是做什么的呢?它就是我们上面说到的“树”结构关系的联系者,include会关联其他的URLconf到本URLconf,也就是说include关联的是孩子节点。整个URL dispatcher体系,就是由这三个方法构建起来的,下面我们重点来介绍这三个方法,了解了这三个方法,整个URL映射机制就会非常清楚了。

def patterns(prefix, *args):
  pass
def url(regex, view, kwargs=None, name=None, prefix=''):
  pass
def include(arg, namespace=None, app_name=None):
  pass

url()

先来看下最重要的url()方法。第一个参数regex是代表URL的正则表达式,第二个参数指定了和该正则表达式映射的View,此外,还可以通过kwargs参数,给view方法指定默认的kwargs参数,还有name参数,用来命名该URL,主要用在URL反解中,至于prefix用处不大,不解释。

url()方法最终构造了一个对象,我们姑且叫它URL映射对象,当第一次访问这个对象去匹配URL时,它会把这个对象中的正则表达式编译一次,然后保存在该对象中,所以以后再次匹配时,就会很快,不会重复编译该正则表达式了。在这里正则匹配其实就是用就是python的re模块,使用过程大致如下:

# 第一次访问时,编译,然后保存在url对象中
regex = re.compile(regex_str, re.UNICODE)
# 每次URL访问时,进行正则匹配
match = regex.search(path)
kwargs = match.groupdict()
args = match.groups()

注意,这里涉及到了上面提到的第二个问题,即URL中的参数是如何获取,如何传递给view方法的。从URL中获取参数,其实是通过re模块中named groups和non-named groups的概念来获取的,通过match.groupdict()得到的是named groups,其实就是一个字典,字典的key是在URL中指定的,该字典会作为kwargs参数传递给view,而通过match.groups()得到的是non-named groups,是一个元组,即tuple,该元组会作为args参数传递给view。不过,这里的args和kwargs是不能够同时存在的,当有kwargs不为空时,args就会被置空,当kwargs为空时,args才会被用到,而传递给view的kwargs就只有url()方法中指定的默认kwargs。也就是说,如果你在URL中使用了named groups,那么non-named groups就会被忽略,如果只使用了non-named groups,它才会被作为args参数,传递给view方法。

好,纠结了一大堆,还是来解析一下我们上面提到的例子,假如我们有url和view

urls:

url(r'^articles/(\d{4})/$', 'news.views.year_archive')
url(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive')

views:

def year_archive(request, *args, **kwargs):
  pass
def month_archive(request, *args, **kwargs):
  pass

当我们访问”articles/2014/”这个路径的时候,解析的过程如下:

>>> import re
>>> regex = re.compile(r'^articles/(\d{4})/$', re.UNICODE)
>>> match = regex.search("articles/2014/")
>>> match.groupdict()
{}
>>> match.groups()
('2014',)

所以最终传递给year_archive()方法中的参数应该是这样的:

(Pdb) pp args
(u'2014',)
(Pdb) pp kwargs
{}

当我们访问”articles/2014/11”这个路径时,解析的过程如下:

>>> import re
>>> regex = re.compile(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', re.UNICODE)
>>> match = regex.search("articles/2014/11/")
>>> match.groupdict()
{'year': '2014', 'month': '11'}
>>> match.groups()
('2014', '11')

所以最终传递给month_archive()方法中的参数应该是这样的:

(Pdb) pp args
()
(Pdb) pp kwargs
{'month': u'11', 'year': u'2014'}

再罗嗦一句,因为url()可以指定一个kwargs参数,它是该url关联的view()方法的默认kwargs参数,也就是说如果在url()方法中指定了kwargs,那么会将这个参数的内容,也传递到view方法中的kwargs参数中。

好,至此,url()方法基本上就清楚了,第二个问题也解决了,至于name参数,到下面讲到URL反解的时候再详细解释。

patterns()

接下来,我们来看patterns()方法,这个其实比较简单,它就是返回一个由url()方法构造的URL映射对象组成的列表。它有一个必填参数是prefix,这个prefix是它所包含的view的公共前缀,这么做是为了避免代码重复,比如:

urlpatterns = patterns('',
  url(r'^articles/(\d{4})/$', 'news.views.year_archive'),
  url(r'^articles/(\d{4})/(\d{2})/$', 'news.views.month_archive'),
  url(r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'news.views.article_detail'),
)

可以写成:

urlpatterns = patterns('news.views',
  url(r'^articles/(\d{4})/$', 'year_archive'),
  url(r'^articles/(\d{4})/(\d{2})/$', 'month_archive'),
  url(r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'article_detail'),
)

注意,由patterns()生成的列表,被赋值给urlpatterns这个变量,这个变量是不能随便定义的,必须是约定好的,默认django会去URLconf中查找这个变量,也许你可以在某个地方设定一个参数,来换个约定,改变一下这个变量名。

在2.0版本的Django中,会舍弃这个方法,而是直接赋值给urlpatterns一个列表,不做过多讨论。

include()

我们来说include(),这其实是个难点,关键在于URL反解那里,Django的文档也没有说清楚,而且关系也比较乱,所以,必须得实际的测试一下,才会明白。

上面说过,include()是“树”结构关系的联系者,include会关联其他的URLconf到本URLconf,靠include()才能够让Django的URL设计变得非常的灵活和简洁。include()有三个参数,第一个参数不必多说,它指定了要包含的其它URLconf的路径,关键是剩下的两个参数,一个是namespace, 一个是app_name,有什么用呢?其实,这两个参数再加上url()方法中的name参数,共同构成了Django中URL的命名空间,而命名空间主要是为了URL反解的,那什么是URL反解呢?我们现在能根据请求的一个URL路径,找到对应的view处理方法,那么反过来,我们在view方法中,或者是template中,根据传递过来的参数,能够解析出对应的URL,这就是URL反解。为什么需要URL反解呢?主要是为了不要把程序写死了,如果我们在html中直接把路径写死了,那么以后改起来就会非常的麻烦,所以常常会把这些可变的东西放到一个变量中,在程序中引用的是这个变量名,这是写程序的一个常识吧。所以,我们能从这个“树”中,从上到下,也得能够从下到上。

在template中进行反解使用的是{%url%}这个tag,在view中,进行反解,使用的是`django.core.urlresolvers.reverse()这个方法。

好,先来看一个最简单的例子:

mydjango/urls.py:

urlpatterns = patterns('',
  url(r'model/', include('model_test.urls')),
)

model_test/urls.py:

urlpatterns = patterns('',
  url(r'^$', views.index, name='index'),
)

mydjango/urls.py是根URLconf,它包含了model_test的URLconf,modul_test中的urlpatterns中有一个命名为index的url映射对象。

如果我们想在template中得到这个view对应的url的真实路径,那么用template的url tag就行了:

{% url 'index' %}

这样得到的结果就是: /model/。

同理,如果在view方法中,那么使用reverve()方法:

from django.core.urlresolvers import reverse
reverse("index")

得到的也是: /model/

在这个例子中,我们只使用到了url()方法中的name参数,并没有用到命名空间,因为这种简单的情况,没有产生混淆,还没有必要用到命名空间,使用命名空间的主要有以下两种情况:

当在一个项目中,有多个应用,应用中定义的url映射对象的name有可能有重复的,这样当进行反解时,Django就不能确定到底是哪个应用了

当在一个项目中,同一个应用,被部署多个实例时,这多个实例之间是共享定义的name url的,所以在进行反解时,也不能确定,到底是哪个实例

第一种情况,其实是比较好解决的,在每一个应用的include()中,指定不同的namespace参数就可以了,如:

mydjango/urls.py:

urlpatterns = patterns('',
  url(r'model/', include('model_test.urls', namespace='model')),
)

这样,在template中或者是reverse中,在name前需要加上namepace进行反解:

{% 'model:index' %}
or
reverse("model:index")

这样就可以准确的反解到model_test这个应用中。

第二种情况,什么叫“一个应用,被部署多个实例”呢?其实就是这种情况:

urlpatterns = patterns('',
  url(r'model1/', include('model_test.urls')),
  url(r'model2/', include('model_test.urls')),
)

不同的路径下,引用的是相同的应用,同一个应用,被实例化了两次,这种情况,怎么进行区分呢?我能像第一种情况一样,在include中指定不同的namespace来解决问题吗?答案是可行的,但是不推荐。要知道,他们引用的是同一个应用,同一个应用意味着什么,意味着代码是一样的,你在同一份代码中,通过if/else来判断该反解到哪个namespace中,这个做法是非常不优雅的,严重违背了Django的DRY原则。

那Django通过什么办法来解决这个问题呢?它通过app_name + namespace + current_app的方式来解决。namespace, app_name分别为include()的第二个和第三个参数,app_name指定这个应用的名称,namespace指定这个应用某个实例的url的命名空间,current_app则是根据请求的路径,解析出的该url的命名空间,也就是namespace,在进行反解时,动态的将该current_app传递给反解的函数中,反解的函数就可以根据这个namespace,来确定应该反解到哪个实例中了。同一个应用的多个实例的app_name应该是相同的,而namespace应该是不同的。可能有点乱了,我们再来举个例子:

mydjango/urls.py:

urlpatterns = patterns('',
  url(r'model1/', include('model_test.urls', namespace='model_1', app_name="app")),
  url(r'model2/', include('model_test.urls', namespace='model_2', app_name="app")),
)

model_test/urls.py:

urlpatterns = patterns('',
  url(r'^$', views.index, name='index'),
)

model_test/views.py:

from django.shortcuts import render
from django.core.urlresolvers import reverse

def index(request):
  current_app = request.resolver_match.namespace
  print reverse("app:index", current_app=current_app)
  return render(request, 'model/index.html', current_app=current_app)

model_test/templates/model/index.html:

{% url 'app:index' %}

在view中,首先获得当前的namespace,然后通过current_app传递给reverse(),reverse就可以知道应该解析到哪个实例中了。同理,在templace中,也需要将current_app传递过去。这样,我们就可以动态的反解URL了:

当我们请求的路径是/model1/时,current_app就是model_1,再根据app_name,就可以准确的反解出该URL为:/model1/,
如果请求的路径是/model2/,那么current_app就是model_2,反解的路径就是/model2/。

虽然有点复杂,但是这种情况用的比较少,google了很久,才把这种情况大概弄清楚,也许理解的有不对的地方,待以后实践去检验,现在关键在于理解这种机制思想。

全文完,更多详细的内容,参见Django官方文档:https://docs.djangoproject.com/en/1.6/topics/http/urls/

总结

以上就是本文关于Python探索之URL Dispatcher实例详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续啊参阅本站:Python探索之Metaclass初步了解、Python编程之Re模块下的函数介绍等,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持。

时间: 2017-10-25

Python urls.py的三种配置写法实例详解

urls.py的配置写法一般有三种方式. 1. 第一种是导入视图的方式,就是 The Django Book 里面样例的写法: from blog.views import index url(r'^nowamagic/', index)  2. 第二种方法是视图处理方法,看代码就知道是怎么回事了. url(r'^nowamagic/', 'test.views.index') 3. 第三种是把模型与视图写在前缀里. urlpatterns = patterns('blog.views', ur

【Python】Python的urllib模块、urllib2模块批量进行网页下载文件

由于需要从某个网页上下载一些PDF文件,但是需要下载的PDF文件有几百个,所以不可能用人工点击来下载.正好Python有相关的模块,所以写了个程序来进行PDF文件的下载,顺便熟悉了Python的urllib模块和ulrllib2模块. 1.问题描述 需要从http://www.cvpapers.com/cvpr2014.html上下载几百个论文的PDF文件,该网页如下图所示: 2.问题解决 通过结合Python的urllib模块和urllib2模块来实现自动下载.代码如下: test.py #!

python 使用get_argument获取url query参数

python 使用get_argument获取url query参数 ornado的每个请求处理程序,我们叫做handler,handler里可以自定义自己的处理程序,其实也就是重写方法,如post,get,get_current_user,send_error等等,这里我们只讲get和post的自定义. 我们都知道,在Tornado里,获得用户的输入,都是一个get_argument搞定,似乎很顺理成章: def post(self): nowamagic = self.get_argumen

python中urllib.unquote乱码的原因与解决方法

发现问题 Python中的urllib模块用来处理url相关的操作,unquote方法对应javascript中的urldecode方法,它对url进行解码,把类似"%xx"的字符替换成单个字符,例如:"%E6%B3%95%E5%9B%BD%E7%BA%A2%E9%85%92"解码后会转换成"法国红酒",但是使用过程中,如果姿势不对,最终转换出来的字符会是乱码"法国红é-". 笔者在一个真实的Tornado应用中就

Python爬取qq music中的音乐url及批量下载

前言 qq music上的音乐还是不少的,有些时候想要下载好听的音乐,但有每次在网页下载都是烦人的登录什么的.于是,来了个qqmusic的爬虫.至少我觉得for循环爬虫,最核心的应该就是找到待爬元素所在url吧.下面开始找吧(讲的不对不要笑我) 实现如下 #寻找url: 这个url可不想其他的网站那么好找.把我给累得不轻,关键是数据多,从那么多数据里面挑出有用的数据,最后组合为music真正的music.昨天做的时候整理的几个中间url: #url1:https://c.y.qq.com/sos

Python 通过URL打开图片实例详解

Python 通过URL打开图片实例详解 不论是用OpenCV还是PIL,skimage等库,在之前做图像处理的时候,几乎都是读取本地的图片.最近尝试爬虫爬取图片,在保存之前,我希望能先快速浏览一遍图片,然后有选择性的保存.这里就需要从url读取图片了.查了很多资料,发现有这么几种方法,这里做个记录. 本文用到的图片URL如下: img_src = 'http://wx2.sinaimg.cn/mw690/ac38503ely1fesz8m0ov6j20qo140dix.jpg' 1.用Open

Python解析、提取url关键字的实例详解

解析url用的类库: python2版本: from urlparse import urlparse import urllib python3版本: from urllib.parse import urlparse import urllib.request 研究了不同的url规则发现:只要在搜索关键字是用=嫁接的,查询的关键在解析后的query里 如果不是用=嫁接,查询的关键在解析后的path里. 解析的规则都是一样的,正则如下:(6中不同情况的组合) 另外host为's.weibo.c

Android 通过网络图片路径查看图片实例详解

Android 通过网络图片路径查看图片实例详解 1.在项目清单中添加网络访问权限 <!--访问网络的权限--> <uses-permission android:name="android.permission.INTERNET"/> 2.获取网络图片数据 /** * 获取网络图片的数据 * @param path 网络图片路径 * @return * @throws Exception */ public static byte[] getImage(Str

python+requests+unittest API接口测试实例(详解)

我在网上查找了下接口测试相关的资料,大都重点是以数据驱动的形式,将用例维护在文本或表格中,而没有说明怎么样去生成想要的用例, 问题: 测试接口时,比如参数a,b,c,我要先测a参数,有(不传,为空,整形,浮点,字符串,object,过短,超长,sql注入)这些情况,其中一种情况就是一条用例,同时要保证b,c的正确,确保a的测试不受b,c参数的错误影响 解决思路: 符合接口规范的参数可以手动去填写,或者准备在代码库中.那些不符合规范的参数(不传,为空,整形,浮点,字符串,object,过短,超长,

python 数据的清理行为实例详解

python 数据的清理行为实例详解 数据清洗主要是指填充缺失数据,消除噪声数据等操作,主要还是通过分析"脏数据"产生的原因和存在形式,利用现有的数据挖掘手段去清洗"脏数据",然后转化为满足数据质量要求或者是应用要求的数据. 1.try 语句还有另外一个可选的子句,它定义了无论在任何情况下都会执行的清理行为. 例如: >>>try: raiseKeyboardInterrupt finally: print('Goodbye, world!') G

Android 录制手机屏幕视频生成GIF图片实例详解

Android 录制手机屏幕视频生成GIF图片实例详解 无图无真相,在我们日常的网络交流中往往需要给交流对象提供直观的显示,而视频是一个很好的方式,但是视频需要播放器,还需要当做文件进行对点传输,并不是很方便.想CSDN这样的博客网站也并不支持在博客里放视频这种方式,除非你贴外链,太烦了不是么.最好是如下图这种gif方式,直观 今天来教大家一个易操作的录制方式.当然,一般只适合Android开发者.因为你需要有AndroidStudio 工具 AndroidStudio(完成手机屏幕的视频录制,

对python读取CT医学图像的实例详解

需要安装OpenCV和SimpleItk. SimpleItk比较简单,直接pip install SimpleItk即可. 代码如下: #coding:utf-8 import SimpleITK as sitk import cv2 #LKDS-00058,-102.655469971,108.188810974,438.759994507,12.2279986879 if __name__ == '__main__': filename = "F:/cancer_solution/data

Python文件操作函数用法实例详解

这篇文章主要介绍了Python文件操作函数用法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 字符编码 二进制和字符之间的转换过程 --> 字符编码 ascii,gbk,shit,fuck 每个国家都有自己的编码方式 美国电脑内存中的编码方式为ascii ; 中国电脑内存中的编码方式为gbk , 美国电脑无法识别中国电脑写的程序 , 中国电脑无法识别美国电脑写的程序 现在硬盘中躺着 ascii/gbk/shit/fuck 编码的文件,

Python命令启动Web服务器实例详解

Python命令启动Web服务器实例详解 利用Python自带的包可以建立简单的web服务器.在DOS里cd到准备做服务器根目录的路径下,输入命令: python -m Web服务器模块 [端口号,默认8000] 例如: python -m SimpleHTTPServer 8080 然后就可以在浏览器中输入 http://localhost:端口号/路径 来访问服务器资源. 例如: http://localhost:8080/index.htm(当然index.htm文件得自己创建) 其他机器

python 二分查找和快速排序实例详解

思想简单,细节颇多:本以为很简单的两个小程序,写起来发现bug频出,留此纪念. #usr/bin/env python def binary_search(lst,t): low=0 height=len(lst)-1 quicksort(lst,0,height) print lst while low<=height: mid = (low+height)/2 if lst[mid] == t: return lst[mid] elif lst[mid]>t: height=mid-1 e