Flask Templates源碼學(xué)習(xí)

Flask中的模版是基于jinja2.所以首先學(xué)習(xí)一下jinja2當(dāng)中的基本概念.

Part 1 Basics of Jinja2


暫時(shí)略

Part 2 demo.py


from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    name = 'Saltriver'
    return render_template('hello.html', name=name)

Part 3


在初始化Flask實(shí)例過程中:

    def __init__(self, import_name, static_path=None, static_url_path=None,
                 static_folder='static', template_folder='templates',
                 instance_path=None, instance_relative_config=False,
                 root_path=None):
        _PackageBoundObject.__init__(self, import_name,
                                     template_folder=template_folder,
                                     root_path=root_path)

首先會初始化_PackageBoundObject.從名字上面可以看出馋嗜,這個(gè)對象是與某個(gè)包綁定了的.下面來看下:

    def __init__(self, import_name, template_folder=None, root_path=None):
        #: The name of the package or module.  Do not change this once
        #: it was set by the constructor.
        self.import_name = import_name

        #: location of the templates.  ``None`` if templates should not be
        #: exposed.
        self.template_folder = template_folder

        if root_path is None:
            root_path = get_root_path(self.import_name)

        #: Where is the app root located?
        self.root_path = root_path

        self._static_folder = None
        self._static_url_path = None

_PackageBoundObject中的init函數(shù)還是比較簡單的.設(shè)置了import_name, root_path. 其中template_folder, _static_folder, _static_url_path為None.
在Flask的init函數(shù)中,設(shè)置了static_folder.

下面看render_template函數(shù):

def render_template(template_name_or_list, **context):
    
    ctx = _app_ctx_stack.top
    ctx.app.update_template_context(context)   # <-----
    return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
                   context, ctx.app)
    def update_template_context(self, context):
       
        # funcs = _default_template_ctx_processor
        funcs = self.template_context_processors[None]
        reqctx = _request_ctx_stack.top
        ...
        orig_ctx = context.copy()
        for func in funcs:
            # func()的效果就是_default_template_ctx_processor().
            # 將g, request, session變量加入到context中.
            context.update(func())
        # 意思是,如果render_template()中的參數(shù)有類似于g=x,request=y,session=z
        # 之類的值的話局服,就優(yōu)先使用參數(shù)中的g.也就是說context['g'] 為參數(shù)中的值
        context.update(orig_ctx)

此時(shí)context中就包括以下4個(gè)key-value:
{'session': <NullSession {}>, 'request': <Request 'http://127.0.0.1:5000/' [GET]>, 'name': 'Saltriver', 'g': <flask.g of 'test_template'>}

接下來是render_template中的_render函數(shù).

    return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
                   context, ctx.app)
def _render(template, context, app):

    before_render_template.send(app, template=template, context=context)
    rv = template.render(context)
    template_rendered.send(app, template=template, context=context)
    return rv

首先看template參數(shù)是如何獲得的:

ctx.app.jinja_env.get_or_select_template(template_name_or_list)

jinja_env是一個(gè)locked_cached_property

    @locked_cached_property
    def jinja_env(self):
        return self.create_jinja_environment()

那么掂僵,什么是locked_cached_property?

class locked_cached_property(object):
    
    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func
        self.lock = RLock()

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        with self.lock:
            value = obj.__dict__.get(self.__name__, _missing)
            if value is _missing:
                value = self.func(obj)
                obj.__dict__[self.__name__] = value
            return value

也就是說jinja_env = locked_cached_property(jinja_env)砌们,jinja_env現(xiàn)在是個(gè)描述符.
但我們訪問jinja_env屬性的時(shí)候肉康,會調(diào)用get方法能岩,obj設(shè)置為ctx.app. obj.dict中并沒有self.name也就是'jinja_env'屬性(type(obj).dict才有這個(gè)屬性).于是value is _missing. 然后執(zhí)行原始函數(shù)募强,value = ctx.app.create_jinja_environment() 并設(shè)置該屬性到ctx.app中. 換句話說株灸,在第一次創(chuàng)建了jinja environment后崇摄,app實(shí)例中就有了這個(gè)屬性,以后就不用每次調(diào)用一次create_jinja_environment. 加鎖是保證多線程安全.下面看create_jinja_environment函數(shù):

    def create_jinja_environment(self):
        
        ...
        # 創(chuàng)建環(huán)境.
        rv = self.jinja_environment(self, **options)  # <-----代碼段1
        rv.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages,
            config=self.config,
            # request, session and g are normally added with the
            # context processor for efficiency reasons but for imported
            # templates we also want the proxies in there.
            request=request,
            session=session,
            g=g
        )
        rv.filters['tojson'] = json.tojson_filter
        return rv

下面看代碼段1慌烧, jinja_environment就是Enviroment:

class Environment(BaseEnvironment):
    
    def __init__(self, app, **options):
        if 'loader' not in options:
            # 創(chuàng)建loader.
            options['loader'] = app.create_global_jinja_loader()   # <-----
        BaseEnvironment.__init__(self, **options)   # <----
        self.app = app

loader就是DispatchingJinjaLoader逐抑,繼承自BaseLoader:

class DispatchingJinjaLoader(BaseLoader):
    def __init__(self, app):
        self.app = app
    ...

于是,rv=Environment也創(chuàng)建完畢了.最后更新rv.globals中的值屹蚊,可以看到環(huán)境中的全局變量有url_for, get_flashed_messages, config, request, session, g.這些全局變量可以在模版中直接使用.
rv.filters['tojson']是什么意思呢厕氨?
====================================================

ctx.app.jinja_env.get_or_select_template(template_name_or_list)

這段代碼的前段部分ctx.app.jinja_env是用來創(chuàng)建Environment.
我們可以查看Environment中的屬性.

>>>with app.test_request_context('/'):
...     print env.loader
...     for key in env.globals:
...         print key, env.globals[key], '\n'
>>>...

env.loader為flask.templating.DispatchingJinjaLoader.
我們可以看到env.globals中還有其他有趣的變量: lipsum, dict, cycler, joiner.
Environment創(chuàng)建好之后,我們看看接下來是怎么加載(loader)模版的.
加載模版就是get_or_select_template()函數(shù).我們查看該函數(shù)的doc:

Does a typecheck and dispatches to select_template() if 
an iterable of template names is given, otherwise to get_template().

對于‘hello.html’這個(gè)模版,該函數(shù)會委托給get_template()函數(shù).再看get_template的doc:

Load a template from the loader. If a loader is configuered this
method ask the loader for the template and returns a Template.

換句話說淑翼,get_template函數(shù)將會使用env.loader來加載模版.
下面我們簡單的深入到env.get_template函數(shù)中看看:

    def get_template(self, name, parent=None, globals=None):
        ...
        return self._load_template(name, self.make_globals(globals))

其中self.make_globals(globals)就是加入了DEFAULT_NAMESPACE:
DEFAULT_NAMESPACE = {
'range': range_type,
'dict': dict,
'lipsum': generate_lorem_ipsum,
'cycler': Cycler,
'joiner': Joiner
}

下面繼續(xù)看_load_template函數(shù):

    def _load_template(self, name, globals):
        ...
        # 開始使用self.loader加載模版.
        template = self.loader.load(self, name, globals)   # <-----
        if self.cache is not None:
            self.cache[cache_key] = template
        return template

self.loader也就是DispatchingJinjaLoader,繼承自BaseLoader.
下面為了不陷入Jinja2的細(xì)節(jié)之中而不能自拔.這里我們只是熟悉一下Jinja2.Loader的API.
官方文檔已經(jīng)寫得很好了,這里我也只是對照官方文檔對重點(diǎn)進(jìn)行了翻譯.
----------Jinja2.Loader API-----------
Loaders負(fù)責(zé)從一個(gè)資源所在地,比如文件系統(tǒng)(file system),加載模版.
Environment會保存編譯后的modules保存到內(nèi)存中,原理類似Python的sys.modules.
不同于sys.modules, Environment中的緩存(cache),會有大小的限制.所有的loaders
都繼承BaseLoader.如果你想創(chuàng)建你自己的loader, 請繼承BaseLoader然后重寫get_source函數(shù).

class jinja2.BaseLoader:
Environment提供了一個(gè)get_template方法腐巢, # <-----Env調(diào)用load與loader調(diào)用load的區(qū)別.
這個(gè)方法會調(diào)用loader的load方法來獲取Template對象.
不用overide BaseLoader的load方法.load方法會調(diào)用get_source方法.
官方文檔給出了一個(gè)class MyLoader(BaseLoader)的示例.
get_source函數(shù)會返回一個(gè)元組,元組里面都有什么東西呢玄括?
(source, filename, uptodate)
其中source應(yīng)該為unicode string 或者 ASCII bytestring.
filename應(yīng)該為name of file on the filesystem.否則為None.
uptodate是用來被調(diào)用冯丙,以確定template是否發(fā)生變化了.通常是個(gè)
lambda函數(shù).


                BaseLoader
                /  /|\
               /  / | \
         MyLoader/  |  \
                /   |  DictLoader 
        FileSystemLoader 
                    |    
                PackageLoader    

在DispatchingJinjaLoader的get_source中,
如果沒有設(shè)置'EXPLAIN_TEMPLATE_LOADING'參數(shù)為True.
就啟用_get_source_fast(所謂快速(默認(rèn))獲取?):

    def _get_source_fast(self, environment, template):
        # loader為FileSystemLoader.
        for srcobj, loader in self._iter_loaders(template):  # 代碼段1
            try:
                # 調(diào)用FileSystemLoader的get_source函數(shù).
                # get_source函數(shù)返回一個(gè)元組.
                return loader.get_source(environment, template)  
            except TemplateNotFound:
                continue
        raise TemplateNotFound(template)

在代碼段1中:

    def _iter_loaders(self, template):
        loader = self.app.jinja_loader  
        if loader is not None:
            yield self.app, loader

        # 暫時(shí)忽略,暫時(shí)不涉及到藍(lán)本.
        for blueprint in self.app.iter_blueprints():
            loader = blueprint.jinja_loader
            if loader is not None:
                yield blueprint, loader

_iter_loaders是個(gè)生成器.可以看到與藍(lán)本有關(guān)的loader就在這里獲取.
jinja_loader在_PackageBoundObject中定義,這里就不貼出代碼,
self.app.jinja_loader返回FileSystemLoader.

于是DispatchingJinjaLoader執(zhí)行完畢.
最后_render函數(shù)中,發(fā)送相應(yīng)信號,對模版進(jìn)行渲染.并返回.

--------- QUESTION ----------
Q1:app.create_jinja_environment()函數(shù)中rv.globals.update()
中有request, session, g. 同時(shí)render_template()函數(shù)中
對ctx.app.update_template_context(context)也進(jìn)行了request,
session, g的更新.這是為什么呢遭京?

A1: 注釋已經(jīng)說了:
# inside the app.create_jinja_environment():
# request, session and g are normally added with the
# context processor for efficiency reasons but for imported
# templates we also want the proxies in there.


Part 4


整個(gè)執(zhí)行框架已經(jīng)有了胃惜,我們再看看其中的一些細(xì)節(jié).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哪雕,隨后出現(xiàn)的幾起案子船殉,更是在濱河造成了極大的恐慌,老刑警劉巖斯嚎,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件利虫,死亡現(xiàn)場離奇詭異,居然都是意外死亡堡僻,警方通過查閱死者的電腦和手機(jī)糠惫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钉疫,“玉大人硼讽,你說我怎么就攤上這事∩螅” “怎么了固阁?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長城菊。 經(jīng)常有香客問我备燃,道長,這世上最難降的妖魔是什么凌唬? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任赚爵,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冀膝。我一直安慰自己,他們只是感情好霎挟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布窝剖。 她就那樣靜靜地躺著,像睡著了一般酥夭。 火紅的嫁衣襯著肌膚如雪赐纱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天熬北,我揣著相機(jī)與錄音疙描,去河邊找鬼。 笑死讶隐,一個(gè)胖子當(dāng)著我的面吹牛起胰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巫延,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼效五,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炉峰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钦扭,沒想到半個(gè)月后嗅骄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婆廊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年迅细,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片否彩。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疯攒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出列荔,到底是詐尸還是另有隱情敬尺,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布贴浙,位于F島的核電站砂吞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崎溃。R本人自食惡果不足惜蜻直,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧概而,春花似錦呼巷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至餐曼,卻和暖如春压储,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背源譬。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工集惋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踩娘。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓刮刑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親霸饲。 傳聞我的和親對象是個(gè)殘疾皇子为朋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容