延缓加载的视图

Flask 通常与装饰器一起使用。装饰器很简单,URL 就在该 URL 的视图函数旁边。然而,这种方法有一个缺点:这意味着所有使用装饰器的代码都必须预先导入,否则 Flask 根本找不到这些函数。

如果你的应用需要非常快地导入(例如在 Google App Engine 或其他类似平台上), 这种装饰器方式可能会成为瓶颈。 当你的应用变复杂、超出这种用法时,可以改用集中式的 URL 映射方式。

实现集中式 URL 映射的方式是使用 add_url_rule() 方法。 与使用装饰器不同,这种方式通过一个单独的文件来配置所有的 URL 与视图函数之间的映射。

转换到集中式 URL Map

设想你当前的应用看起来是这样的:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    pass

@app.route('/user/<username>')
def user(username):
    pass

使用集中式方式后,你可以将视图函数写在一个文件中(如 views.py),但不加装饰器:

def index():
    pass

def user(username):
    pass

然后使用另一个文件来创建应用对象,并将视图函数与 URL 映射:

from flask import Flask
from yourapplication import views
app = Flask(__name__)
app.add_url_rule('/', view_func=views.index)
app.add_url_rule('/user/<username>', view_func=views.user)

延迟加载

目前为止,我们只是将视图函数与路由拆分了,但视图模块仍然在启动时被加载。 我们可以进一步优化,只在首次请求时再按需加载视图函数。 为此,我们可以定义一个行为类似函数的类,它在首次调用时动态导入真正的函数:

from werkzeug.utils import import_string, cached_property

class LazyView(object):

    def __init__(self, import_name):
        self.__module__, self.__name__ = import_name.rsplit('.', 1)
        self.import_name = import_name

    @cached_property
    def view(self):
        return import_string(self.import_name)

    def __call__(self, *args, **kwargs):
        return self.view(*args, **kwargs)

关键点在于你需要正确设置 __module____name__, Flask 内部会使用这两个属性来为 URL 规则命名(如果你没有手动指定名称的话)。

接下来,你可以在集中式文件中像下面这样组合视图:

from flask import Flask
from yourapplication.helpers import LazyView
app = Flask(__name__)
app.add_url_rule('/',
                 view_func=LazyView('yourapplication.views.index'))
app.add_url_rule('/user/<username>',
                 view_func=LazyView('yourapplication.views.user'))

你还可以进一步简化代码,比如写一个辅助函数, 自动调用 add_url_rule(), 为 rule name 添加项目名前缀,同时根据需要自动将 view_func 包装为 LazyView。:

def url(import_name, url_rules=[], **options):
    view = LazyView(f"yourapplication.{import_name}")
    for url_rule in url_rules:
        app.add_url_rule(url_rule, view_func=view, **options)

# add a single route to the index view
url('views.index', ['/'])

# add two routes to a single function endpoint
url_rules = ['/user/','/user/<username>']
url('views.user', url_rules)

需要注意的是,before request 和 after request 处理器必须写在启动时就被导入的文件中, 才能在第一次请求时正确生效。任何其他装饰器也是一样。