Tornado的url-handler映射中可以使用字符串

25 Sep, 2012

这个标题真是含糊,因为琢磨了很久都不知道该怎么描述,不过看了下面的说明,就明白到底在说什么了。在使用 Tornado 中,必须将 url-handler 元组组成的列表传给 Application 以便实例化,其中 url-handler 如下面所示:

[
    ('/', HomeHandler),
    ('/([a-zA-Z0-9-]+)/*', EntryHandler),
    ('/picky/([a-zA-Z0-9-]+)/*', PickyHandler),
]

不过在 Django 中,也是使用类似的方法来建立url到相应处理方法的对应,不过 Django 中是这样来做的:

urlpatterns = patterns('',
    url(r'^$', 'mysite.views.bar'),
    url(r'^mysite/', 'mysite.views.foo'),
)

唯一的区别是 Django 使用的对应处理方法用的是字符串,这样明显的好处就是不用在 urls.py 文件中繁琐的导入需要的模块了,比如上面 Tornado 的例子中,如果在独立的文件中设置映射,就必须像下面这样:

from blog import HomeHandler, EntryHandler, PickyHandler

#上面的映射列表

毫无疑问,这样不是一点麻烦,那么 Tornado 能否像 Django 那样呢,当然可以,在 Tornado 的源代码中找到了答案,代码见这里。它判定 handler 是字符类型,则使用 import_object 函数来通过字符串调用相应的模块,不过需要注意的是,模块需要从项目的根开始,最上面的那个例子属于 blog 这个包下,于是可以这样改写:

[
    ('/', 'blog.HomeHandler'),
    ('/([a-zA-Z0-9-]+)/*', 'blog.EntryHandler'),
    ('/picky/([a-zA-Z0-9-]+)/*', 'blog.PickyHandler'),
]

这样就不用去显示的导入那些该死的模块,瞬间觉得轻松多了。

浅析 Python 字符串形式的模块导入

现在把焦点集中在 import_object 函数上,该函数是这样定义的:

def import_object(name):
    parts = name.split('.')
    obj = __import__('.'.join(parts[:-1]), None, None, [parts[-1]], 0)
    return getattr(obj, parts[-1])

其中用到了 __import__ 这个内建函数,主要用来根据字符串导入模块,最大的好处就像上面的那个示例一样,可以使程序在运行时动态加载一些模块,而不是在py文件的开头使用 import 来加载。下面通过几个例子来简单的说明一下这个函数的用法,在 Python 中导入模块有下面这样的形式:

import pkg
import pkg.foo
from pkg import foo, bar
from pkg.foo import func
from pkg.foo import submod

在第一和第二种上,import导入将使用最左边的模块名来作为使用导入模块的本地名称,也就是说如果要使用 foofunc 函数必须这样写 pkg.foo.func();如果使用的是第三种,则可以直接使用 foo.func() 。如果使用 __import__ 函数来导入,第一、二种形式是一样的,只需要这样:

pkg = __import__('pkg')
pkg = __import__('pkg.foo')

由于 __import__ 只会返回一个类型,所以在第三种情况下,需要使用一个临时变量:

tmp = __import__('pkg')
foo = tmp.foo
bar = tmp.bar

不过貌似这样就和我们的初衷不一样了,我们只是想导入 foo|bar 两个子模块,那就需要像下面这样,使用 __import__ 的参数 fromlist

tmp = __import__('pkg', fromlist=['foo', 'bar'])
mod = tmp.foo
mod2 = tmp.bar

最后一种就比较简单了,就如上面一样,不过我们可以不使用临时变量,使用 getattr 就行了:

submod = getattr(__import__('pkg.foo', fromlist=['submod']), 'submod')

嗯,就这样。