关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。

Github:https://github.com/hylinux1024

微信公众号:终身开发者(angrycode)

前面对Flask启动流程路由原理都进行了源码走读。今天我们看看模板渲染的过程。

0x00 使用模板

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

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)

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

/templates
/hello.html

hello.html的内容为

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% 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

@internalcode
def 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()方法

@internalcode
def _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

@internalcode
def 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 学习资料

Python Web Flask源码解读(三)——模板渲染过程的更多相关文章

  1. Python Web Flask源码解读(一)——启动流程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. Python Web Flask源码解读(二)——路由原理

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  3. Python Web Flask源码解读(四)——全局变量

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  4. go语言 nsq源码解读三 nsqlookupd源码nsqlookupd.go

    从本节开始,将逐步阅读nsq各模块的代码. 读一份代码,我的思路一般是: 1.了解用法,知道了怎么使用,对理解代码有宏观上有很大帮助. 2.了解各大模块的功能特点,同时再想想,如果让自己来实现这些模块 ...

  5. Flask源码解读--所有可扩展点

    一.前言 flask中有很多可扩展点(笔者这样称呼),其中包含了信号和请求钩子,这些信号和钩子有什么用呢?其主要作用用于帮助我们进行程序的耦合性,当然还可以让我们自定义一些行为.话不多说,通过阅读源码 ...

  6. jQuery源码解读三选择器

    直接上jQuery源码截取代码 // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ i ...

  7. mysql源码解读之事务提交过程(二)

    上一篇文章我介绍了在关闭binlog的情况下,事务提交的大概流程.之所以关闭binlog,是因为开启binlog后事务提交流程会变成两阶段提交,这里的两阶段提交并不涉及分布式事务,当然mysql把它称 ...

  8. .NET框架源码解读之SSCLI编译过程简介

    前文演示了编译SSCLI最简便的方法(在Windows下): 在“Visual Studio 2005 Command Prompt”下,进入SSCLI的根目录: 运行 env.bat 脚本准备环境: ...

  9. mysql源码解读之事务提交过程(一)

    mysql是一种关系型数据库,关系型数据库一个重要的特性就是支持事务,这是区别于no-sql产品的一个核心特性.当然了,no-sql产品支持键值查询,不能支持sql语句,这也是一个区别.今天主要讨论下 ...

随机推荐

  1. 02、HTML 基础

    1.HTML 标题 HTML 标题(Heading)是通过<h1> - <h6> 标签来定义的. <!DOCTYPE html> <html> < ...

  2. [ PyQt入门教程 ] Qt Designer工具的使用

    Qt Designer是PyQt程序UI界面的实现工具,Qt Designer工具使用简单,可以通过拖拽和点击完成复杂界面设计,并且设计完成的.ui程序可以转换成.py文件供python程序调用.本文 ...

  3. 2019全国大学生信息安全与对抗技术竞赛全国线下总决赛 Writeup

    0x00 Begin 关于 ISCC 2019 北理工总决赛,这一次比赛体验感总体差不多,最后我们战队荣获全国一等奖第一名,在这里非常感谢我的团队以及我的队友. 0x01 Reverse 下载题目:e ...

  4. tp3 的前端内置标签

    Volist 标签 volist标签通常用于查询数据集(select 方法),对于查询出来的结果数组进行遍历输出. 首先赋值: $User = M("User"); $list = ...

  5. android 基于wifi模块通信开发

    这篇文章主要是我写完手机与wifi模块通信后所用来总结编写过程的文章,下面,我分几点来说一下编写的大概流程. 一.拉出按钮控件并设置它的点击事件 二.设置wifi权限 三.打开和关闭wifi 四.扫描 ...

  6. 真千兆路由的极限之OPENWRT MAKE, 某品牌白菜价QCA9558/QCA9880/QCA8337N纯种组合OS搭建时记

    自从上次仙人梦里放了一张无字天书,解惑了WPR003N的秘诀后,渐渐的,就忘了这件这事情,连想好的评测都拖延了好多月了,毕竟路由是拿来用的,不是用来写什么陈词滥调的评测的,无意间,热爱白菜的我发现了一 ...

  7. 基于 Autojs 的 APP、小程序自动化测试 SDK - 2019年8月3日

    原文:https://blog.csdn.net/laobingm/article/details/98317394 autojs sdk基于 Autojs 的 APP.小程序自动化测试 SDK,支持 ...

  8. mysql 学习第一天

    RDBMS 术语 在我们开始学习MySQL 数据库前,让我们先了解下RDBMS的一些术语: 数据库: 数据库是一些关联表的集合. 数据表: 表是数据的矩阵.在一个数据库中的表看起来像一个简单的电子表格 ...

  9. Java编程基础阶段笔记 day 07 面向对象编程(上)

    ​ 面向对象编程 笔记Notes 面向对象三条学习主线 面向过程 VS 面向对象 类和对象 创建对象例子 面向对象的内存分析 类的属性:成员变量 成员变量 VS 局部变量 类的方法 方法的重载 可变个 ...

  10. 9-1、大型项目的接口自动化实践记录----数据库结果、JSON对比

    上一篇写了如何从DB获取预期.实际结果,这一篇分别对不同情况说下怎么进行对比. PS:这部分在JSON对比中也适用. 1.结果只有一张表,只有一条数据 数据格式:因为返回的是dicts_list的格式 ...