關於我
一個有思想的程序猿,終身學習實踐者,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。
Github:https://github.com/hylinux1024
微信公衆號:終身開發者(angrycode)html
前面對Flask
啓動流程和路由原理都進行了源碼走讀。今天咱們看看模板渲染的過程。python
首先看一個來自官方文檔使用模板渲染的例子linux
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
文件git
/templates /hello.html
hello.html
的內容爲github
<!doctype html> <title>Hello from Flask</title> {% if name %} <h1>Hello {{ name }}!</h1> {% else %} <h1>Hello, World!</h1> {% endif %}
這個模板中name
是參數,經過調用render_template
方法就能夠根據參數實現html
模板文件的渲染。shell
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()
中被調用的session
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