关于我
编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是咱们团队的主要技术栈。
Github:github.com/hylinux1024
微信公众号:angrycodehtml
前面对Flask
启动流程和路由原理都进行了源码走读。今天咱们看看模板渲染的过程。linux
首先看一个来自官方文档使用模板渲染的例子git
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
文件github
/templates
/hello.html
复制代码
hello.html
的内容为shell
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
复制代码
这个模板中name
是参数,经过调用render_template
方法就能够根据参数实现html
模板文件的渲染。编程
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
是经过如下语句初始化flask
_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
的实例,这个类的做用就是从文件系统中加载模板文件的。
@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
的实例(默认状况下)。
@internalcode
def load(self, environment, name, globals=None):
...
# 省略部分源码
return environment.template_class.from_code(environment, code, globals, uptodate)
复制代码
BaseLoader
是FileSystemLoader
的基类。这个load
方法实现了模板的编译、加载等逻辑。最后是使用environment.template_class.from_code()
方法。其中template_class
是Template
类,它表明编译后的模板对象。 from_code
是Template
类的静态方法,能够用来建立一个Template
实例。当load
方法返回时,就获得了一个Template
对象。 最后回到render_template
方法
def render_template(template_name, **context):
...
return current_app.jinja_env.get_template(template_name).render(context)
复制代码
执行了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
**函数。exec
是Python
内置函数,它能够动态的执行Python
代码。
Flask
使用Jinja
做为模板引擎。执行路径为
Flask.render_template => Environment.get_template => Template.render => exec
复制代码