本課程由ekCit釋出在實驗樓,完整教程及線上練習地址:Python實現模板引擎

一、課程介紹

1。 內容簡介

模版引擎使得使用者介面能夠與業務資料分離,前端與後端分離,它通常用於渲染頁面檔案。本課程將使用Python實現一個具備基礎功能的模板引擎。

2。 課程知識點

本課程專案完成過程中,我們將學習:

實現模版引擎的原理與方法

如何編寫程式生成程式碼

3。 課程來源

本課程核心部分來自《500 lines or less》專案,作者是來自 edX 的工程師 Ned Batchelder,這是他的部落格:http://nedbatchelder。com/ 。專案程式碼使用 MIT 協議,專案文件使用

http://

creativecommons。org/lic

enses/by/3。0/legalcode

協議。課程內容在原文件基礎上做了稍許修改,增加了部分原理介紹,步驟的拆解分析及原始碼註釋。

二、實驗環境

開啟終端,進入 Code 目錄,建立 template-engine 資料夾, 並將其作為我們的工作目錄。

$ cd Code

$ mkdir template-engine && cd template-engine

三、實驗原理

模板介紹

大部分程式都存在大量的邏輯設計和少部分的文字資料,程式語言為此而生。但有時候也會遇到只需要少部分邏輯設計但卻要處理大量文字資料的情況,對於這類,還是有一個專門的工具來處理比較好,模板引擎為此而生。

以Web應用為例,它在伺服器端生成html頁面由客戶端的瀏覽器解析渲染。因為大量的頁面存在使用者名稱、商品列表、朋友圈動態等動態資料,所以就需要程式動態地生成頁面。並且我們也希望靜態文字(html標記)的部分能夠完全交由前端設計師完成,後端只用專心負責動態文字的生成。

先從一個簡單的例子開始,我們想要生成以下HTML文字:

Welcome, Charlie!

Products:

  • Apple: $1。00
  • Fig: $1。50
  • Pomegranate: $3。25

其中出現的動態文字有使用者姓名,商品名,商品售價。同時商品種類的數量也並不是固定的。

為了生成這段HTML文字,我們將靜態文字作為字串常量儲存,動態文字透過format格式化進字串:

# 頁面的主要文字,其中name和products是動態的部分

PAGE_HTML = “”“

Welcome, {name}!

Products:

    {products}

”“”

# 商品項的的主要文字,prodname與price是動態部分

PRODUCT_HTML = “

  • {prodname}: {price}
  • \n”

    def make_page(username, products):

    #儲存商品列表文字

    product_html = “”

    for prodname, price in products:

    product_html += PRODUCT_HTML。format(

    prodname=prodname, price=format_price(price))

    html = PAGE_HTML。format(name=username, products=product_html)

    return html

    雖然這段程式碼奏效了,但是看上去有點混亂。靜態文字被分成了PAGE_HTML與PRODUCT_HTML兩部分,而且動態資料格式化的細節操作都在Python程式碼中,使得前端設計師不得不去修改Python檔案。隨著程式碼量的增多,這段程式碼也會漸漸變得難以維護。

    更好的做法是使用模板,整個html檔案就可以作為一個模板,需要動態文字填充的部分就使用標籤標識。

    模板化後的模板檔案(html檔案):

    Welcome, {{user_name}}!

    Products:

      {% for product in product_list %}

    • {{ product。name }}:

      {{ product。price|format_price }}

    • {% endfor %}

    對比之前的程式碼是HTML文字嵌在Python程式碼中,現在的程式碼是把HTML文字的部分完全拿了出來而在其中嵌了少許邏輯。

    我們知道字串可以透過format函式將資料代入,而模板將format的功能進行了擴充套件,它可以支援條件判斷與迴圈等邏輯,因此模版也可以看作是format的高階版吧。

    想要在程式中使用HTML模板,首先需要一個模板引擎,模版引擎能夠結合HTML模板與上下文環境(包括準備匯入的資料等)生成完整的html頁面,它的工作就是解析模板,將其中動態的部分與資料進行替換。

    當然模板引擎不一定非用在生成html頁面上,它就是一個生成文字的引擎,用在什麼型別的文字上都是合適的。

    模板使用的語法

    模板引擎因其支援的語法而異,本課程中使用的引擎語法基於Django - 一個非常流行的web框架。

    代入資料使用雙花括號:

    Welcome, {{user_name}}!

    有時候我們希望傳入一個物件或者詞典,那在模板中要怎麼取得物件的屬性或者詞典的鍵值呢。

    如果是在Python中,取得的語法都是不一樣的。

    dict[“key”]

    obj。attr

    obj。method()

    而在模版中,則是統一使用點:

    dict。key

    obj。attr

    obj。method

    用例如下:

    The price is: {{product。price}}, with a {{product。discount}}% discount。

    你可以使用過濾器過濾修改資料,過濾器使用管道符號呼叫,用例如下:

    Short name: {{story。subject|slugify|lower}}

    在模板中使用條件判斷:

    {% if user。is_logged_in %}

    Welcome, {{ user。name }}!

    {% endif %}

    在模板中使用for迴圈:

    Products:

      {% for product in product_list %}

    • {{ product。name }}: {{ product。price|format_price }}
    • {% endfor %}

    就像一般的程式一樣,判斷與迴圈可以巢狀組成更復雜的邏輯。

    在模版中使用註釋:

    {# This is the best template ever! #}

    引擎的實現方法

    大方向上,模板的處理流程可以分為兩部分:解析階段與渲染階段。

    渲染模板需要考慮以下幾方面:

    管理資料來源(即上下文環境)

    處理邏輯(條件判斷、迴圈)的部分

    實現點取得成員屬性或者鍵值的功能、實現過濾器呼叫

    問題的關鍵在於從解析階段到渲染階段是如何過渡的。解析得到了什麼?渲染又是在渲染什麼?解析階段可能有兩種不同的做法:解釋或者是編譯,這正對應了我們的程式語言的實現方式。

    在解釋型模型中,解析階段最後會生成能夠反映模板結構的資料結構。渲染階段會遍歷整個資料結構並基於預設的指令集生成最後的結果文字。Django使用的模板引擎使用的就是這種方式。

    在編譯模型中,解析階段最後會生成某種可直接執行的程式碼。渲染階段可直接執行程式碼得到結果文字。Jinja2與Mako就是使用這種方式的兩個典型。

    我們使用第二種,也就是編譯的方式來實現我們的模板引擎:我們會先將模板編譯成Python程式碼,然後再透過執行這段程式碼生成結果文字。

    編譯生成Python函式

    先來看一個從模板到Python函式的例子,還是拿之前的例子舉例。

    模版文字:

    Welcome, {{user_name}}!

    Products:

      {% for product in product_list %}

    • {{ product。name }}:

      {{ product。price|format_price }}

    • {% endfor %}

    模板編譯後生成的Python函式:

    def render_function(context, do_dots):

    c_user_name = context[‘user_name’]

    c_product_list = context[‘product_list’]

    c_format_price = context[‘format_price’]

    result = []

    append_result = result。append

    extend_result = result。extend

    to_str = str

    extend_result([

    Welcome, ’,

    to_str(c_user_name),

    ‘!

    \n

    Products:

    \n
      \n’

      ])

      for c_product in c_product_list:

      extend_result([

      ‘\n

    • ’,

      to_str(do_dots(c_product, ‘name’)),

      ‘:\n ’,

      to_str(c_format_price(do_dots(c_product, ‘price’))),

    • \n’

      ])

      append_result(‘\n

    \n’)

    return ‘’。join(result)

    每一個模版都會被轉換為render_function函式,其中context上下文環境儲存匯入的資料詞典。do_dots儲存用來取得物件屬性或者詞典鍵值的函式。

    我們從頭開始分析這段程式碼,最開始是對輸入的資料詞典進行解包,得到的每個變數都使用c_作為字首。先使用佇列來儲存結果,append與extend可能會在在程式碼中多次用到,所以使用append_result與extend_result來引用它們,這樣會比平時直接使用append少一次檢索的開銷。之後就是使用append_result與extend_result把結果串起來,其中需要替換的部分就用輸入資料替換,最後把佇列合成一個字串作為結果文字返回。

    使用變數引用append來得到一點點效能上的最佳化(微最佳化)可以算是某種奇技淫巧,它是以犧牲可讀性為代價的,新手程式設計師儘量不要在這型別的最佳化上花太多功夫,這類最佳化只推薦在已經被前人證明確實能夠提升效能且是有益的情況下使用。

    你可能還注意到了to_str = str這句,它也是一種微最佳化,Python檢索區域性空間比檢索內建空間早,所以把str儲存在區域性變數中也是一種最佳化。

    四、實驗步驟

    本專案的詳細步驟和程式碼詳解,可在實驗樓中檢視並在線完成,立即【開始實驗】

    更多Python經典專案:Python全部 - 課程

    Python實現模板引擎

    Python實現模板引擎