python web 模版_Python Web Flask源码解读!模板渲染过程!

关于我

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

微信公众号:angrycode

Python资源共享群:484031800

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

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

@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)

复制代码

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() 方法。

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 **函数。 exec 是 Python 内置函数,它可以动态的执行 Python 代码。

0x05 总结一下

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

Flask.render_template => Environment.get_template => Template.render => exec

复制代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值