jinja2 應(yīng)該是 Python 里面最著名的模板渲染引擎了,,并且提到 jinja2,很多人會立刻想到 flask,,因為 flask 在渲染模板的時候用的就是它,。
但 jinja2 不和 flask 綁定,,它是獨立于 flask 存在的,這就使得 jinja2 可以應(yīng)用在很多地方,。像一些能夠生成 html 的繪圖框架、分析工具,,內(nèi)部很多都使用了 jinja2,,比如 pandas, pyecharts 等等。
那么下面就來單獨地介紹一下 jinja2 的模板渲染語法,。
先來看看最簡單的字符串替換: import jinja2
# 將想要替換的內(nèi)容使用 {{}} 包起來 string = "姓名: {{name}}, 年齡: {{age}}"
# 得到模板對象 temp = jinja2.Template(string)
# 調(diào)用 render 方法進行渲染 # 返回渲染之后的字符串 render_string = temp.render(name="古明地覺", age=16) print(render_string) """ 姓名: 古明地覺, 年齡: 16 """
用法相當(dāng)簡單,,并且和字符串格式化非常類似: string = "姓名: {name}, 年齡: {age}"
render_string = string.format(name="古明地覺", age=16) print(render_string) """ 姓名: 古明地覺, 年齡: 16 """
兩者非常類似,只不過字符串格式化使用的是一對大括號,,而 jinja2 使用的是兩對大括號,。但如果只是簡單的字符串替換,那么使用 jinja2 就有點大材小用了,,因為 jinja2 支持的功能遠不止這些,。 import jinja2
string = "姓名: {{info['name']}}, 年齡: {{info['age']}}"
temp = jinja2.Template(string) render_string = temp.render( info={"name": "古明地覺", "age": 16}) print(render_string) """ 姓名: 古明地覺, 年齡: 16 """
可以看到 jinja2 不僅僅支持字符串替換,在替換的時候還可以做一些額外的操作,,并且這些操作不止局限于字典(以及其它對象)的取值,,算術(shù)運算、函數(shù)調(diào)用也是支持的,。 import jinja2
string = """{{numbers * 3}} {{tuple1 + tuple2}} {{np.array([1, 2, 3])}} """
temp = jinja2.Template(string) render_string = temp.render( numbers=[1, 2, 3], tuple1=("a", "b"), tuple2=("c", "d"), np=__import__("numpy") ) print(render_string) """ [1, 2, 3, 1, 2, 3, 1, 2, 3] ('a', 'b', 'c', 'd') [1 2 3] """
還是很強大的,,但有兩個注意的點,首先我們不能定義空的花括號,。 import jinja2
string = "---{{}}---"
try: temp = jinja2.Template(string) except jinja2.TemplateSyntaxError as e: print(e) """ Expected an expression, got 'end of print statement' """
{{}} 內(nèi)部必須要指定相應(yīng)的參數(shù),否則報錯,。但如果指定了參數(shù),,在渲染的時候不傳,會怎么樣呢,? import jinja2
string = "---{{name}}---"
temp = jinja2.Template(string) render_string = temp.render() print(render_string) """ ------ """
我們看到不傳也不會報錯,,在渲染的時候會直接丟棄,按照空來處理,??扇绻麉?shù)進行了某種操作,那么就必須要給參數(shù)傳值了,,舉個例子,。 import jinja2
# 對參數(shù) name 進行了操作, 所以必須傳值 string = "---{{name.upper()}}---"
temp = jinja2.Template(string) render_string = temp.render(name="satori") print(render_string) """ ---SATORI--- """
try: temp.render() except jinja2.UndefinedError as e: print(e) """ 'name' is undefined """
關(guān)于模板傳參就說到這里,還是很簡單的,。
過濾器的概念應(yīng)該不需要多說,,在 jinja2 中通過 | 來實現(xiàn)過濾器,。 例如:{{name | length}},會返回 name 的長度,。過濾器相當(dāng)于是一個函數(shù),,參數(shù)接收到的值會傳到過濾器中,然后過濾器根據(jù)自己的功能再返回相應(yīng)的值,,最后將結(jié)果渲染到頁面中,。 jinja2 內(nèi)置了很多的過濾器,下面介紹一些常用的: import jinja2
string = """{{array}} 的長度 -> {{array|length}} {{array}} 的總和 -> {{array|sum}} {{array}} 的第一個元素 -> {{array|first}} {{array}} 的最后一個元素 -> {{array|last}} {{array}} 使用 {{sep}} 拼接的字符串 -> {{array|join(sep)}} {{count}} 轉(zhuǎn)成整數(shù) -> {{count|int}} {{count}} 轉(zhuǎn)成浮點數(shù) -> {{count|float}} {{count}} 轉(zhuǎn)成字符串 -> {{count|string}} {{count}} 的絕對值 -> {{count|abs}} {{name}} 轉(zhuǎn)成小寫 -> {{name|lower}} {{name}} 轉(zhuǎn)成大寫 -> {{name|upper}} {{name}} 的 'i' 替換成 'I' -> {{name|replace('i', "I")}} {{name}} 反向取值 -> {{name|reverse}}
字符串過長, 使用省略號表示 最多顯示 10 位 -> {{long_text|truncate(length=10)}} """
temp = jinja2.Template(string) render_string = temp.render( array=[1, 2, 3, 4, 5], sep='_', count=-666.66, name="Koishi", long_text="a" * 100 ) print(render_string) """ [1, 2, 3, 4, 5] 的長度 -> 5 [1, 2, 3, 4, 5] 的總和 -> 15 [1, 2, 3, 4, 5] 的第一個元素 -> 1 [1, 2, 3, 4, 5] 的最后一個元素 -> 5 [1, 2, 3, 4, 5] 使用 _ 拼接的字符串 -> 1_2_3_4_5 -666.66 轉(zhuǎn)成整數(shù) -> -666 -666.66 轉(zhuǎn)成浮點數(shù) -> -666.66 -666.66 轉(zhuǎn)成字符串 -> -666.66 -666.66 的絕對值 -> 666.66 Koishi 轉(zhuǎn)成小寫 -> koishi Koishi 轉(zhuǎn)成大寫 -> KOISHI Koishi 的 'i' 替換成 'I' -> KoIshI Koishi 反向取值 -> ihsioK
字符串過長, 使用省略號表示 最多顯示 10 位 -> aaaaaaa... """
以上就是 jinja2 內(nèi)置的一些常用的過濾器,,然后還有一個特殊的過濾器 default,。前面說了,如果不給 {{}} 里面的參數(shù)傳值的話,,那么默認會不顯示,,也不報錯。但如果我們希望在不傳遞的時候,,使用默認值該怎么辦呢,? import jinja2
string = "{{sign|default('這個人很懶,什么也沒留下')}}"
temp = jinja2.Template(string) render_string = temp.render( sign="不裝了,,攤牌了,,我就是高級特工氚疝鉀" ) print(render_string) """ 不裝了,攤牌了,,我就是高級特工氚疝鉀 """
# 不指定,,會使用默認值 render_string = temp.render() print(render_string) """ 這個人很懶,什么也沒留下 """
default 還有一個參數(shù) boolean,,因為 default 是否執(zhí)行,,不在于傳的是什么值,而在于有沒有傳值,。只要傳了,,就不會顯示 default 里面的內(nèi)容。 那如果我想當(dāng)傳入空字符串,,空字典等等,,在 Python 中為假的值,還是等價于沒傳值,,繼續(xù)顯示 default 里的默認值,,該怎么辦呢? 很簡單,,可以將參數(shù) boolean 指定為 True,,表示只有當(dāng)布爾值為真時,才使用我們傳遞的值,。否則,,仍顯示 default 里的默認值,。 import jinja2
string = "{{sign1|default('這個人很懶,什么也沒留下')}}\n" \ "{{sign2|default('這個人很懶,,什么也沒留下', boolean=True)}}"
temp = jinja2.Template(string) # sign1 和 sign2 接收的都是空字典,,布爾值為假 render_string = temp.render( sign1={}, sign2={} ) # 對于 sign1 而言,只要傳值了,,就會顯示我們傳的值 # 對于 sign2 而言,,不僅要求傳值,還要求布爾值為真,,否則還是會使用默認值 print(render_string) """ {} 這個人很懶,,什么也沒留下 """
可以看到 jinja2 內(nèi)置了很多的過濾器,但如果我們的業(yè)務(wù)場景比較特殊,,jinja2 內(nèi)置的過濾器滿足不了,,該怎么辦呢?沒關(guān)系,,jinja2 還支持我們自定制過濾器,。
過濾器本質(zhì)上就是個函數(shù),因此我們只需要寫個函數(shù),,定義相應(yīng)的邏輯,,然后注冊到 jinja2 過濾器當(dāng)中即可。下面我們手動實現(xiàn)一個 replace 過濾器,。 import jinja2
string = "{{name|my_replace('i', 'I')}}"
# 定義過濾器對應(yīng)的函數(shù) # jinja2 在渲染的時候,,就會執(zhí)行這里的 my_replace 函數(shù) def my_replace(s, old, new): """ 需要一提的是,過濾器里面接收了兩個參數(shù) 但函數(shù)要定義三個參數(shù),,因為在調(diào)用的時候 name 也會傳過來 所以像 {{name|length}} 這種,,它和 {{name|length()}} 是等價的 函數(shù)至少要能接收一個參數(shù) """ return s.replace(old, new)
# 此時函數(shù)就定義好了,但它目前和過濾器還沒有什么關(guān)系,,只是名字一樣而已 # 我們還需要將過濾器和函數(shù)注冊到 jinja2 當(dāng)中
# 這里調(diào)用了一個新的類 Environment # jinja2.Template 本質(zhì)上也是調(diào)用了 Environment env = jinja2.Environment() # 將過濾器和函數(shù)綁定起來,,注冊到 jinja2 當(dāng)中 # 并且過濾器的名字和函數(shù)名可以不一樣 env.filters["my_replace"] = my_replace # 返回 Template 對象 temp = env.from_string(string) # 調(diào)用 render 方法渲染 render_string = temp.render(name="koishi") print(render_string) """ koIshI """
Environment 是 jinja2 的核心組件,包含了配置,、過濾器、全局環(huán)境等一系列重要的共享變量,。如果我們想自定制過濾器的話,,那么必須手動實例化這個對象,然后注冊進去,。通過調(diào)用它的 from_string 方法,,得到 Template 對象,這樣在渲染的時候就能找到我們自定制的過濾器了,。
事實上,,我們之前在實例化 Template 對象時,,底層也是這么做的。
因此我們后續(xù)就使用 Environment 這個類,,當(dāng)然 Template 也是可以的,。 jinja2 還支持 if、for 等邏輯語句,,來看一下,。 import jinja2
# 如果是接收具體的值,那么使用 {{}} # 但 if,、for 等邏輯語句,,則需要寫在 {% %} 里面 string = """ {% if info['math'] >= 90 %} {{info['name']}} 的數(shù)學(xué)成績?yōu)?nbsp;A {% elif info['math'] >= 80 %} {{info['name']}} 的數(shù)學(xué)成績?yōu)?nbsp;B {% elif info['math'] >= 60 %} {{info['name']}} 的數(shù)學(xué)成績?yōu)?nbsp;C {% else %} {{info['name']}} 的數(shù)學(xué)成績?yōu)?nbsp;D {% endif %}""" # 和 Python 的 if 語句類似 # 但是結(jié)尾要有一個 {%endif %}
env = jinja2.Environment() temp = env.from_string(string) render_string = temp.render( info={"math": 85, "name": "古明地覺"} ) print(render_string) """ 古明地覺 的數(shù)學(xué)成績?yōu)?nbsp;B """
render_string = temp.render( info={"math": 9, "name": "琪露諾"} ) print(render_string) """ 琪露諾 的數(shù)學(xué)成績?yōu)?nbsp;D """
并且 if 語句還可以多重嵌套,都是可以的,。
然后是 for 語句: import jinja2
# 和 Python for 循環(huán)等價 # 但不要忘記結(jié)尾的 {% endfor %} string = """ {% for girl in girls %} 姓名: {{girl['name']}}, 地址: {{girl['address']}} {% endfor %} """
env = jinja2.Environment() temp = env.from_string(string) render_string = temp.render( girls=[{"name": "古明地覺", "address": "地靈殿"}, {"name": "琪露諾", "address": "霧之湖"}, {"name": "魔理沙", "address": "魔法森林"}] ) print(render_string) """ 姓名: 古明地覺, 地址: 地靈殿
姓名: 琪露諾, 地址: 霧之湖
姓名: 魔理沙, 地址: 魔法森林 """
所以 {% for girl in girls %} 這段邏輯和 Python 是等價的,,先確定 girls 的值,然后遍歷,。因此在里面也可以使用過濾器,,比如 {% for girl in girls|reverse %} 便可實現(xiàn)對 girls 的反向遍歷。 如果在遍歷的時候,,還想獲取索引呢,? import jinja2
string = """ {% for name, address in girls.items()|reverse %} -------------------------------- 姓名: {{name}}, 地址: {{address}} 索引(從1開始): {{loop.index}} 索引(從0開始): {{loop.index0}} 是否是第一次迭代: {{loop.first}} 是否是最后一次迭代: {{loop.last}} 序列的長度: {{loop.length}} -------------------------------- {% endfor %} """
env = jinja2.Environment() temp = env.from_string(string) render_string = temp.render(girls={"古明地覺": "地靈殿", "琪露諾": "霧之湖", "魔理沙": "魔法森林"}) print(render_string) """ -------------------------------- 姓名: 魔理沙, 地址: 魔法森林 索引(從1開始): 1 索引(從0開始): 0 是否是第一次迭代: True 是否是最后一次迭代: False 序列的長度: 3 --------------------------------
-------------------------------- 姓名: 琪露諾, 地址: 霧之湖 索引(從1開始): 2 索引(從0開始): 1 是否是第一次迭代: False 是否是最后一次迭代: False 序列的長度: 3 --------------------------------
-------------------------------- 姓名: 古明地覺, 地址: 地靈殿 索引(從1開始): 3 索引(從0開始): 2 是否是第一次迭代: False 是否是最后一次迭代: True 序列的長度: 3 --------------------------------
"""
可以看到 jinja2 還是很強大的,因為它不僅僅是簡單的替換,,而是一個模板渲染引擎,,并且內(nèi)部還涉及到編譯原理。jinja2 也是先通過 lexing 進行分詞,,然后 parser 解析成 AST,,再基于 optimizer 優(yōu)化AST,最后在當(dāng)前的環(huán)境中執(zhí)行,。
所以 jinja2 一般用于渲染 HTML 頁面等大型文本內(nèi)容,,那么問題來了,如果有一個 HTML 文本,,jinja2 要如何加載它呢,? from jinja2 import Environment, FileSystemLoader env = Environment( # 指定一個加載器,里面?zhèn)魅胨阉髀窂?/span> loader=FileSystemLoader(".") )
# 在指定的路徑中查找文件并打開,,同樣會返回 Template 對象 temp = env.get_template("login.html") # 注意:此處不能手動調(diào)用 Template # 如果是手動調(diào)用 Template("login.html") 的話 # 那么 "login.html" 會被當(dāng)成是普通的字符串
print(temp.render()) """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>歡迎來到古明地覺的編程教室</h3> </body> </html> """
當(dāng)然啦,,不管是以普通字符串的形式,還是以文本文件的形式,,jinja2 的語法都是不變的,。
宏,說得通俗一點,,就類似于函數(shù),。我們給一系列操作進行一個封裝,,再給一個名字,然后調(diào)用宏名的時候,,就會執(zhí)行預(yù)定義好的一系列操作,。 from jinja2 import Environment env = Environment()
# 通過 {% macro %} 可以定義一個宏 # 這里的宏叫 input,當(dāng)然叫什么無所謂 string = """ {% macro input(name, value="", type="text") %} <input type="{{type}}" name="{{name}}" value="{{value}}"/> {% endmacro %}
{{input("satori","東方地靈殿")}} {{input("marisa","魔法森林")}} {{input("", "提交", "submit")}} """ temp = env.from_string(string) print(temp.render().strip()) """ <input type="text" name="satori" value="東方地靈殿"/>
<input type="text" name="marisa" value="魔法森林"/>
<input type="submit" name="" value="提交"/> """
此外宏也是可以導(dǎo)入的,,既然涉及到導(dǎo)入,,那么就需要寫在文件里面了。而之所以要有宏的導(dǎo)入,,也是為了分文件編程,,這樣看起來更加清晰。
marco.html {% macro input(name, value="", type="text") %} <input type="{{type}}" name="{{name}}" value="{{value}}"/> {% endmacro %}
這樣我們就把宏單獨定義在一個文件里面,,先通過 import "宏文件的路徑" as xxx 來導(dǎo)入宏,,然后再通過 xxx.宏名 調(diào)用即可。注意這里必須要起名字,,也就是必須要 as,。或者 from "宏文件的路徑" import 宏名 [as xxx],,這里起別名則是可選的,。 login.html {% import "macro.html" as macro %}
{{macro.input("satori","東方地靈殿")}} {{macro.input("mashiro","櫻花莊的寵物女孩")}} {{macro.input("", "提交", "submit")}}
然后 Python 代碼和之前類似,直接加載 login.html 然后渲染即可,。
include 的使用就很簡單了,,相當(dāng)于 Ctrl + C 和 Ctrl + V。 1.txt 古明地覺,,一個幽靈也為之懼怕的少女 但當(dāng)你推開地靈殿的大門,,卻發(fā)現(xiàn)...
2.txt {% include "1.txt" %} 她居然在調(diào)戲她的妹妹
我們使用的文件一直都是 html 文件,但 txt 文件也是可以的,。 from jinja2 import Environment, FileSystemLoader env = Environment( loader=FileSystemLoader(".") )
temp = env.get_template("2.txt") print(temp.render()) """ 古明地覺,,一個幽靈也為之懼怕的少女 但當(dāng)你推開地靈殿的大門,卻發(fā)現(xiàn)... 她居然在調(diào)戲她的妹妹 """
所以 include 就相當(dāng)于將文件里的內(nèi)容復(fù)制粘貼過來,。
在模板中,,我們還可以定義一個變量,然后在其它的地方用,。 from jinja2 import Environment env = Environment()
string = """ {% set username="satori" %} <h2>{{username}}</h2>
{% with username="koishi" %} <h2>{{username}}</h2> {% endwith %}
<h2>{{username}}</h2> """ temp = env.from_string(string) # 使用 set 設(shè)置變量,,在全局都可以使用 # 使用 with 設(shè)置變量,那么變量只會在 with 語句塊內(nèi)生效 # 所以結(jié)尾才要有 {% endwith %} 構(gòu)成一個語句塊 print(temp.render().strip()) """ <h2>satori</h2>
<h2>koishi</h2>
<h2>satori</h2> """
此外 with 還有另一種寫法: {% with %} {% set username = "koishi" %} {% endwith %}
這樣寫也是沒問題的,,因為 set 在 with 里面,所以變量只會在 with 語句塊內(nèi)生效,。 對于很多網(wǎng)站的頁面來說,,它的四周有很多內(nèi)容都是不變的,,如果每來一個頁面都要寫一遍的話,會很麻煩,。因此我們可以將不變的部分先寫好,,在變的部分留一個坑,這就是父模板,。然后子模板繼承的時候,,會將父模板不變的部分繼承過來,然后將變的部分,,也就是父模板中挖的坑填好,。總結(jié)一下就是:父模板挖坑,,子模板填坑,。 base.html <p>古明地覺:我的家在哪呢?</p> {% block 古明地覺 %} {% endblock %}
<p>魔理沙:我的家在哪呢,?</p> {% block 魔理沙 %} {% endblock %}
<p>芙蘭朵露:我的家在哪呢,?</p> {% block 芙蘭朵露 %} {% endblock %}
<p>找不到家的話,就跟我走吧</p>
child.html {% extends "base.html" %}
{% block 古明地覺 %} <p>你的家在地靈殿</p> {% endblock %}
{% block 魔理沙 %} <p>你的家在魔法森林</p> {% endblock %}
{% block 芙蘭朵露 %} <p>你的家在紅魔館</p> {% endblock %}
在 base.html 里面通過 {% block %} 挖坑,,子模板繼承過來之后再填坑,。以下是 child.html 渲染之后的內(nèi)容:
執(zhí)行結(jié)果沒有問題,并且父模板在挖坑的時候,,如果里面有內(nèi)容,,那么子模板在繼承之后會自動清除,但也可以使用 {{super()}} 保留下來,。 base.html <p>古明地覺:我的家在哪呢,?</p> {% block 古明地覺 %} <p>父模板挖坑時填的內(nèi)容</p> {% endblock %}
<p>魔理沙:我的家在哪呢?</p> {% block 魔理沙 %} {% endblock %}
child.html {% extends "base.html" %}
{% block 古明地覺 %} <p>子模板繼承的時候,,默認會清空父模板的內(nèi)容</p> <p>但可以通過 super 保留下來,,以下是父模板寫入的內(nèi)容</p> {{super()}} {% endblock %}
{% block 魔理沙 %} <p>通過 self.塊名() 可以在一個塊內(nèi)引用其它塊的內(nèi)容</p> <p>以下是 古明地覺 塊里的內(nèi)容</p> {{self.古明地覺()}} {% endblock %}
以下是 child.html 渲染之后的內(nèi)容:
可以看到,在引用其它塊的內(nèi)容時,,會把其它塊繼承的父模板的內(nèi)容一塊引用過來,。因為一旦繼承,那么就變成自己的了,。 最后 {% extend "xxx.html" %} 要放在最上面,,不然容易出問題,在 Django 會直接報錯,。另外子模板中的代碼一定要放在 block 語句塊內(nèi),,如果放在了外面,jinja2 是不會渲染的。
以上就是 jinja2 相關(guān)的內(nèi)容,,當(dāng)我們希望按照指定規(guī)則生成文件時,,不妨讓 jinja2 來替你完成任務(wù)吧。 作為一款模板渲染引擎,,jinja2 無疑是最出名的,,但其實 jinja2 是借鑒了 Django 的模板渲染引擎。只不過 Django 的引擎和 Django 本身是強耦合的,,而 jinja2 是獨立存在的,,這也使得它可以應(yīng)用在除 web 框架之外的很多地方。
|