博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python Web Flask源码解读(三)——模板渲染过程
阅读量:4511 次
发布时间:2019-06-08

本文共 8435 字,大约阅读时间需要 28 分钟。

关于我

编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
Github:
微信公众号:angrycode

前面对和都进行了源码走读。今天我们看看模板渲染的过程。

0x00 使用模板

首先看一个来自官方文档使用模板渲染的例子

from flask import render_template@app.route('/hello/')@app.route('/hello/
')def hello(name=None): return render_template('hello.html', name=name)复制代码

在项目目录下需要有一个templates目录,并创建了一个hello.html文件

/templates    /hello.html复制代码

hello.html的内容为

Hello from Flask{% if name %}  

Hello {
{ name }}!

{% else %}

Hello, World!

{% endif %}复制代码

这个模板中name是参数,通过调用render_template方法就可以根据参数实现html模板文件的渲染。

0x01 Flask.render_template

def render_template(template_name, **context):    """Renders a template from the template folder with the given    context.    :param template_name: the name of the template to be rendered    :param context: the variables that should be available in the                    context of the template.    """    current_app.update_template_context(context)    return current_app.jinja_env.get_template(template_name).render(context)复制代码

方法的注释很清楚,从templates文件夹中找到名称为template_name的文件进行渲染。其中current_app是通过以下语句初始化

_request_ctx_stack = LocalStack()current_app = LocalProxy(lambda: _request_ctx_stack.top.app)复制代码

LocalStack就是一个栈的实现类。而_request_ctx_stack是在Flask.request_context()方法中将当前的上下文实例push到栈里面的

def request_context(self, environ):    """Creates a request context from the given environment and binds    it to the current context.  This must be used in combination with    the `with` statement because the request is only bound to the    current context for the duration of the `with` block.    Example usage::        with app.request_context(environ):            do_something_with(request)    :params environ: a WSGI environment    """    return _RequestContext(self, environ)复制代码

_RequestContext类实现了上下文管理器协议,它可以在with语句中使用

class _RequestContext(object):    """The request context contains all request relevant information.  It is    created at the beginning of the request and pushed to the    `_request_ctx_stack` and removed at the end of it.  It will create the    URL adapter and request object for the WSGI environment provided.    """    def __init__(self, app, environ):        self.app = app        self.url_adapter = app.url_map.bind_to_environ(environ)        self.request = app.request_class(environ)        self.session = app.open_session(self.request)        self.g = _RequestGlobals()        self.flashes = None    def __enter__(self):        _request_ctx_stack.push(self)    def __exit__(self, exc_type, exc_value, tb):        # do not pop the request stack if we are in debug mode and an        # exception happened.  This will allow the debugger to still        # access the request object in the interactive shell.        if tb is None or not self.app.debug:            _request_ctx_stack.pop()复制代码

执行__enter__()时操作push,退出with语句时就执行pop操作。

回到request_context()方法,它是在wsgi_app()中被调用的

def wsgi_app(self, environ, start_response):    """The actual WSGI application.  This is not implemented in    `__call__` so that middlewares can be applied:        app.wsgi_app = MyMiddleware(app.wsgi_app)    :param environ: a WSGI environment    :param start_response: a callable accepting a status code,                           a list of headers and an optional                           exception context to start the response    """    with self.request_context(environ):        rv = self.preprocess_request()        if rv is None:            rv = self.dispatch_request()        response = self.make_response(rv)        response = self.process_response(response)        return response(environ, start_response)复制代码

从文章的分析知道,wsgi_app()在服务端接收到客户端请求时就会执行。 所以当请求来临时,就会把当前Flask实例的请求上下文实例保存到栈实例_request_ctx_stack中;请求处理后,就从栈里面弹出当前请求的上下文实例。

LocalProxy是一个代理类,它的构造函数传递了一个lambda表达式:lambda: _request_ctx_stack.top.app。 这个操作就把当前的上下文实例通过LocalProxy进行了封装,即current_app是当前Flask实例的上下文的代理。 所以当current_app.jinja_env这个语句其实就是访问Flask的实例属性jinja_env,这个属性是在Flask的构造函数中进行初始化的。

class Flask(object):    ...    #: 源码太长了省略    #: options that are passed directly to the Jinja2 environment    jinja_options = dict(        autoescape=True,        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']    )    def __init__(self, package_name):        ...        #: 源码太长省略部分源码        #: the Jinja2 environment.  It is created from the        #: :attr:`jinja_options` and the loader that is returned        #: by the :meth:`create_jinja_loader` function.        self.jinja_env = Environment(loader=self.create_jinja_loader(),                                     **self.jinja_options)        self.jinja_env.globals.update(            url_for=url_for,            get_flashed_messages=get_flashed_messages        )复制代码

jinja_env是一个Environment实例。这个是jinja模板引擎提供的类,Flask框架的模板渲染就是通过jinja来实现的。 Environment需要一个loader,是通过以下方法获取的

def create_jinja_loader(self):    """Creates the Jinja loader.  By default just a package loader for    the configured package is returned that looks up templates in the    `templates` folder.  To add other loaders it's possible to    override this method.    """    if pkg_resources is None:        return FileSystemLoader(os.path.join(self.root_path, 'templates'))    return PackageLoader(self.package_name)复制代码

默认情况下是从templates目录下构造一个FileSystemLoader的实例,这个类的作用就是从文件系统中加载模板文件的。

0x02 Environment.get_template

@internalcodedef get_template(self, name, parent=None, globals=None):    """Load a template from the loader.  If a loader is configured this    method ask the loader for the template and returns a :class:`Template`.    If the `parent` parameter is not `None`, :meth:`join_path` is called    to get the real template name before loading.    The `globals` parameter can be used to provide template wide globals.    These variables are available in the context at render time.    If the template does not exist a :exc:`TemplateNotFound` exception is    raised.    .. versionchanged:: 2.4       If `name` is a :class:`Template` object it is returned from the       function unchanged.    """    if isinstance(name, Template):        return name    if parent is not None:        name = self.join_path(name, parent)    return self._load_template(name, self.make_globals(globals))复制代码

get_template()方法内部调用了_load_template()方法

@internalcodedef _load_template(self, name, globals):    if self.loader is None:        raise TypeError('no loader for this environment specified')    if self.cache is not None:        template = self.cache.get(name)        if template is not None and (not self.auto_reload or \                                     template.is_up_to_date):            return template    template = self.loader.load(self, name, globals)    if self.cache is not None:        self.cache[name] = template    return template复制代码

_load_template()方法首先会检查是否有缓存,如果缓存可用就使用缓存;缓存不可用就使用loader加载模板,这个loader就是前面提到的FileSystemLoader的实例(默认情况下)。

0x03 BaseLoader.load

@internalcodedef load(self, environment, name, globals=None):    ...    # 省略部分源码    return environment.template_class.from_code(environment, code, globals, uptodate)复制代码

BaseLoaderFileSystemLoader的基类。这个load方法实现了模板的编译、加载等逻辑。最后是使用environment.template_class.from_code()方法。其中template_classTemplate类,它代表编译后的模板对象。 from_codeTemplate类的静态方法,可以用来创建一个Template实例。当load方法返回时,就得到了一个Template对象。 最后回到render_template方法

def render_template(template_name, **context):    ...    return current_app.jinja_env.get_template(template_name).render(context)复制代码

执行了Template对象的render()方法。

0x04 Template.render

def render(self, *args, **kwargs):    """This function accepts either a dict or some keyword arguments which    will then be the context the template is evaluated in.  The return    value will be the rendered template.    :param context: the function accepts the same arguments as the                    :class:`dict` constructor.    :return: the rendered template as string    """    ns = self.default_context.copy()    if len(args) == 1 and isinstance(args[0], utils.MultiDict):        ns.update(args[0].to_dict(flat=True))    else:        ns.update(dict(*args))    if kwargs:        ns.update(kwargs)    context = Context(ns, self.charset, self.errors)    exec self.code in context.runtime, context    return context.get_value(self.unicode_mode)复制代码

这个方法接收一个dict类型参数,用于给模板传递参数。该方法的**核心是执行exec**函数。execPython内置函数,它可以动态的执行Python代码。

0x05 总结一下

Flask使用Jinja作为模板引擎。执行路径为

Flask.render_template => Environment.get_template => Template.render => exec复制代码

0x06 学习资料

转载于:https://juejin.im/post/5d38fc1af265da1bb31c7b31

你可能感兴趣的文章
Android 动画效果 及 自定义动画
查看>>
const与#define相比有什么不同?
查看>>
Eclipse4.7 SpringIDE插件的安装
查看>>
C#面向对象基础
查看>>
Jquery页面加载效果
查看>>
ios对new Date() 的兼容问题
查看>>
Charles常用设置
查看>>
filebeat
查看>>
如何在Bitmap中画图?(MFC)
查看>>
Windows 用来定位 DLL 的搜索路径
查看>>
Backbone 学习笔记五
查看>>
R语言:各种零碎
查看>>
Mysql5.7修改root密码
查看>>
WC2019退役失败记
查看>>
Centos6.6下安装nginx1.6.3
查看>>
iOS开发之多线程
查看>>
[算法竞赛]第七章_暴力求解法
查看>>
MorkDown 常用语法总结
查看>>
sqlserver生成随机数 2011-12-21 15:47 QQ空间
查看>>
jQuery禁止鼠标右键
查看>>