web.py原始碼分析: 模板(1)

diabloneo發表於2015-02-01

web.py模板的實現原理

web.py的模板實現利用了Python的可執行物件的動態特性:根據模板內容和渲染函式的引數建立一個函式,該函式執行的時候會返回一個TemplateResult類例項,該例項的字串化就是模板對應的HTML內容

實驗環境搭建

為了說明web.py的模板是如何實現的,我們需要在web.py的模板實現程式碼中加入一些列印語句來顯示中間結果。Python的virtualenv工具很好的實現了這個需求。另外,我還使用了iPython,不過Python標準命令列也是可以的。環境搭建的步驟簡述如下:

  • 建立virtualenv環境:virtualenv env

  • 啟用virtualenv環境:cd env以及source bin/activate

  • 安裝web.py:pip install web.py

這個web.py庫會被安裝在virtualenv環境的目錄下:

(env)➜ ~/programming/python/env/lib/python2.7/site-packages/web  $ pwd
/home/diabloneo/programming/python/env/lib/python2.7/site-packages/web

下面就可以修改這個web.py的程式碼來看看模板到底是如何實現的。

實驗程式碼修改

我們要修改的程式碼位於web/template.py檔案內,找到Template類的compile_template函式(在template.py檔案的第900行),加入一行列印語句:

def compile_template(self, template_string, filename):
    code = Template.generate_code(template_string, filename, parser=self.create_parser())

    def get_source_line(filename, lineno):
        try:
            lines = open(filename).read().splitlines()
            return lines[lineno]
        except:
            return None

    print code  # 這行就是我們增加的除錯語句,可以列印出前面提到的動態生成的函式。
    try:
        # compile the code first to report the errors, if any, with the filename
        compiled_code = compile(code, filename, `exec`)
    except SyntaxError, e:
    ...
    

模板函式到底長什麼樣?

下面我們就可以來看看模板函式到底長什麼樣了。當然,首先得建立一個模板檔案。在我們的實驗環境中進行如下操作:

(env)➜ ~/programming/python/env  $ ls
bin  include  lib  local
(env)➜ ~/programming/python/env  $ mkdir app
(env)➜ ~/programming/python/env  $ ls
app bin  include  lib  local
(env)➜ ~/programming/python/env  $ cd app
(env)➜ ~/programming/python/env/app  $ mkdir templates

現在,在templates目錄下建立一個最簡單的模板,檔名為hello.html,內容如下:

hello, world

下面來看看根據這個模板生成的函式到底長什麼樣子,在實驗環境下啟動ipython或者python,進入到app目錄:

(env)➜ ~/programming/python/env/app  $ ipython
WARNING: Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv.
Python 2.7.8 (default, Oct 20 2014, 15:05:19)
Type "copyright", "credits" or "license" for more information.

IPython 1.0.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython`s features.
%quickref -> Quick reference.
help      -> Python`s own help system.
object?   -> Details about `object`, use `object??` for extra details.

In [1]:

執行如下程式碼就可以看到模板函式的內容:

In [3]: hello = web.template.frender("templates/hello.html")
# coding: utf-8
def __template__():
    __lineoffset__ = -5
    loop = ForLoop()
    self = TemplateResult(); extend_ = self.extend
    extend_([u`hello, world
`])

    return self

In [4]:

上面的函式__template__()就是我們增加到web.py庫中的那行print code所列印出來的。從這個函式的定義,我們可以看出:

  1. 函式內使用了一些預定義的物件:ForLoop,TemplateResult等。

  2. 函式的結果是返回一個TemplateResult例項。

那麼,現在問題來了:

  1. 這個函式是如何生成的?

  2. 使用的這些預定義名稱從哪裡來的?

  3. TemplateResult例項的字串表示如何變成HTML文字的?

本文先來說明TemplateResult例項是如何產生HTML文字這個問題。

TemplateResult

TemplateResult類的定義也在web/template.py檔案中,繼承的類和實現的方法如下:

class TemplateResult(object, DictMixin):
   __delattr__ : function
   __delitem__ : function
   __getattr__ : function
   __getitem__ : function
   __init__ : function
   __repr__ : function
   __setattr__ : function
   __setitem__ : function
   __str__ : function
   __unicode__ : function
   _prepare_body : function
   keys : function

其中DictMixin是一個實現了大部分字典操作的類,繼承該類的子類(也就是這裡的TemplateResult)需要實現:__getitem__(), __setitem__(), __delitem__()和keys()方法,以便物件可以模擬完整的字典操作。需要說明的是:DictMixin類已經過時了,現在應該使用collections.MutableMapping類(該類的實現利用了abc庫–抽象類)。

先來看__init__()函式,

def __init__(self, *a, **kw):
    self.__dict__["_d"] = dict(*a, **kw)
    self._d.setdefault("__body__", u``)

    self.__dict__[`_parts`] = []
    self.__dict__["extend"] = self._parts.extend

從初始化函式可以看出,TemplateResult大部分屬性都儲存在_d這個字典中,該字典至少包含一個元素body。所以,外部程式碼對TemplateResult的物件屬性進行增刪改查操作時,實際上都是在操作內部的_d這個字典。初始化函式中定義的另外兩個屬性是:_parts,一個序列;extend,指向_parts序列的extend()方法,也就是說呼叫TemplateResult例項的extend方法實際上是呼叫例項屬性_parts的extend方法。這個extend方法我們在前面的__template__()函式已經見到過了:

def __template__():
    __lineoffset__ = -5
    loop = ForLoop()
    self = TemplateResult(); extend_ = self.extend
    extend_([u`hello, world
`])

    return self
    

這個函式定義了extend_ = self.extendextend_把模板的內容新增到了self._parts這個序列中。

下面來看一下TemplateResult如何生成HTML內容,這個就要看__str__()方法了:

def _prepare_body(self):
    """Prepare value of __body__ by joining parts.
    """
    if self._parts:
        value = u"".join(self._parts)
        self._parts[:] = []
        body = self._d.get(`__body__`)
        if body:
            self._d[`__body__`] = body + value
        else:
            self._d[`__body__`] = value

def __str__(self):
    self._prepare_body()
    return self["__body__"].encode(`utf-8`)

主要操作是在_prepare_body()函式裡,主要操作是把_parts中的字串拼接起來,然後再拼接到body的內容後面。

看過TemplateResult的實現後,我們可以知道,由模板生成的__template__()函式最終會把一堆字串新增到TemplateResult例項中,然後再通過例項生成HTML字串。

總結

通過列印中間結果和分析程式碼,我們已經大概知道了web.py的模板是如何轉化成HTML內容的。下一篇文章會闡述web.py模板的各種語法所對應的動態函式內容。

相關文章