电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

如何用python寫個模板引擎

瀏覽:71日期:2022-06-29 18:44:41
一.實(shí)現(xiàn)思路

本文講解如何使用python實(shí)現(xiàn)一個簡單的模板引擎, 支持傳入變量, 使用if判斷和for循環(huán)語句, 最終能達(dá)到下面這樣的效果:

渲染前的文本:<h1>{{title}}</h1><p>十以內(nèi)的奇數(shù):</p><ul>{% for i in range(10) %} {% if i%2==1 %} <li>{{i}}</li> {% end %}{% end %}</ul>渲染后的文本,假設(shè)title='高等數(shù)學(xué)':<h1>高等數(shù)學(xué)</h1><p>十以內(nèi)的奇數(shù):</p><ul><li>1</li><li>3</li><li>5</li><li>7</li><li>9</li></ul>

要實(shí)現(xiàn)這樣的效果, 第一步就應(yīng)該將文本中的html代碼和類似{% xxx %}這樣的渲染語句分別提取出來, 使用下面的正則表達(dá)式可以做到:

re.split(r’(?s)({{.*?}}|{%.*?%}|{#.*?#})’, html)

用這個正則表達(dá)式處理剛才的文本, 結(jié)果如下:

如何用python寫個模板引擎

在提取文本之后, 就需要執(zhí)行內(nèi)部的邏輯了. python自帶的exec函數(shù)可以執(zhí)行字符串格式的代碼:

exec(’print('hello world')’) # 這條語句會輸出hello world

因此, 提取到html的渲染語句之后, 可以把它改成python代碼的格式, 然后使用exec函數(shù)去運(yùn)行. 但是, exec函數(shù)不能返回代碼的執(zhí)行結(jié)果, 它只會返回None. 雖然如此, 我們可以使用下面的方式獲取字符串代碼中的變量:

global_namespace = {}code = '''a = 1def func(): pass'''exec(code, global_namespace)print(global_namespace) # {’a’: 1, ’func’: <function func at 0x00007fc61e3462a0>, ’__builtins__’: <module ’builtins’ (built-in)>}

因此, 我們只要在code這個字符串中定義一個函數(shù), 讓它能夠返回渲染后的模板, 然后使用剛才的方式把這個函數(shù)從字符串中提取出來并執(zhí)行, 就能得到結(jié)果了.

基于上面的思路, 我們最終應(yīng)該把html文本轉(zhuǎn)化為下面這樣的字符串:

# 這個函數(shù)不是我們寫的, 是待渲染的html字符串轉(zhuǎn)化過來的def render(context: dict) -> str: result = [] # 這一部分負(fù)責(zé)提取所有動態(tài)變量的值 title = context[’title’] # 對于所有的html代碼或者是變量, 直接放入result列表中 result.extend([’<h1>’, str(title), ’</h1>n<p>十以內(nèi)的奇數(shù):</p>n<ul>n’]) # 對于模板中的for和if循環(huán)語句,則是轉(zhuǎn)化為原生的python語句 for i in range(10): if i % 2 == 1: result.extend([’n <li>’, str(i), ’</li>n ’]) result.append(’n</ul>’) # 最后,讓函數(shù)將result列表聯(lián)結(jié)為字符串返回就行, 這樣就得到了渲染好的html文本 return ’’.join(result)

如何將html文本轉(zhuǎn)化為上面這樣的代碼, 是這篇文章的關(guān)鍵. 上面的代碼是由最開始那個html demo轉(zhuǎn)化來的, 每一塊我都做了注釋. 如果沒看明白的話, 就多看幾遍, 不然肯定是看不懂下文的.

總的來說, 要渲染一個模板, 思路如下:

如何用python寫個模板引擎

二.字符串代碼

為了能夠方便地生成python代碼, 我們首先定義一個CodeBuilder類:

class CodeBuilder: INDENT_STEP = 4 def __init__(self, indent_level: int = 0) -> None: self.indent_level = indent_level self.code = [] self.global_namespace = None def start_func(self) -> None: self.add_line(’def render(context: dict) -> str:’) self.indent() self.add_line(’result = []’) self.add_line(’append_result = result.append’) self.add_line(’extend_result = result.extend’) self.add_line(’to_str = str’) def end_func(self) -> None: self.add_line('return ’’.join(result)') self.dedent() def add_section(self) -> ’CodeBuilder’: section = CodeBuilder(self.indent_level) self.code.append(section) return section def __str__(self) -> str: return ’’.join(str(line) for line in self.code) def add_line(self, line: str) -> None: self.code.extend([’ ’ * self.indent_level + line + ’n’]) def indent(self) -> None: self.indent_level += self.INDENT_STEP def dedent(self) -> None: self.indent_level -= self.INDENT_STEP def get_globals(self) -> dict: if self.global_namespace is None: self.global_namespace = {} python_source = str(self) exec(python_source, self.global_namespace) return self.global_namespace

這個類作為字符串代碼的容器使用, 它的本質(zhì)是對字符串代碼的封裝, 在字符串的基礎(chǔ)上增加了以下的功能:

代碼縮進(jìn)CodeBuilder維護(hù)了一個indent_level變量, 當(dāng)調(diào)用它的add_line方法寫入新代碼的時候, 它會自動在代碼開頭加上縮進(jìn). 另外, 調(diào)用indent和dedent方法就能方便地增加和減少縮進(jìn).

生成函數(shù)由于定義這個類的目的就是在字符串里面寫一個函數(shù), 而這個函數(shù)的開頭和結(jié)尾都是固定的, 所以把它直接寫到對象的方法里面. 值得一提的是, 在start_func這個方法中, 我們寫了這樣三行代碼:

append_result = result.appendextend_result = result.extendto_str = str

這樣做是為了提高渲染模板的性能, 調(diào)用我們自己定義的函數(shù), 需要的時間比調(diào)用result.append或者str等函數(shù)的時間少. 首先對于列表的append和extend兩個方法來說, 每調(diào)用一次, python都需要在列表中的所有方法中找一次, 而直接把它綁定到我們自己定義的變量上, 就能避免python重復(fù)地去列表的方法中來找. 然后是str函數(shù), 理論上, python查找局部變量的速度比查找內(nèi)置變量的快, 因此我們使用一個局部變量to_str, python找到它的速度就比找str要快.

上面這段話都是我從網(wǎng)上看到的, 實(shí)際測試了一下, 在python3.7上, 運(yùn)行append_result需要的時間比直接調(diào)用result.append少了大約25%, to_str則沒有明顯的優(yōu)化效果.

代碼嵌套有的時候我們需要在一塊代碼中嵌套另外一塊代碼, 這時候可以調(diào)用add_section方法, 這個方法會創(chuàng)建一個新的CodeBuilder對象作為內(nèi)容插入到原CodeBuilder對象里面, 這個和前端的div套div差不多.

這個方法的好處是, 你可以在一個CodeBuilder對象中預(yù)先插入一個CodeBuilder對象而不用寫入內(nèi)容, 相當(dāng)于先占著位置. 等條件成熟之后, 再回過頭來寫入內(nèi)容. 這樣就增加了字符串代碼的可編輯性.

獲取變量調(diào)用get_globals方法獲取當(dāng)前字符串代碼內(nèi)的所有全局變量.

三.Template模板

在字符串代碼的容器做好之后, 我們只需要解析html文本, 然后把它轉(zhuǎn)化為python代碼放到這個容器里面就行了. 因此, 我們定義如下的Template類:

class Template: html_regex = re.compile(r’(?s)({{.*?}}|{%.*?%}|{#.*?#})’) valid_name_regex = re.compile(r’[_a-zA-Z][_a-zA-Z0-9]*$’) def __init__(self, html: str, context: dict = None) -> None: self.context = context or {} self.code = CodeBuilder() self.all_vars = set() self.loop_vars = set() self.code.start_func() vars_code = self.code.add_section() buffered = [] def flush_output() -> None: if len(buffered) == 1:self.code.add_line(f’append_result({buffered[0]})’) elif len(buffered) > 1:self.code.add_line(f’extend_result([{', '.join(buffered)}])’) del buffered[:] strings = re.split(self.html_regex, html) for string in strings: if string.startswith(’{%’):flush_output()words = string[2:-2].strip().split()ops = words[0]if ops == ’if’: if len(words) != 2: self._syntax_error('Don’t understand if', string) self.code.add_line(f’if {words[1]}:’) self.code.indent()elif ops == ’for’: if len(words) != 4 or words[2] != ’in’: self._syntax_error('Don’t understand for', string) i = words[1] iter_obj = words[3] # 這里被迭代的對象可以是一個變量,也可以是列表,元組或者range之類的東西,因此使用_variable來檢驗(yàn) try: self._variable(iter_obj, self.all_vars) except TemplateSyntaxError: pass self._variable(i, self.loop_vars) self.code.add_line(f’for {i} in {iter_obj}:’) self.code.indent()elif ops == ’end’: if len(words) != 1: self._syntax_error('Don’t understand end', string) self.code.dedent()else: self._syntax_error('Don’t understand tag', ops) elif string.startswith(’{{’):expr = string[2:-2].strip()self._variable(expr, self.all_vars)buffered.append(f’to_str({expr})’) else:if string.strip(): # 這里使用repr把換行符什么的改成/n的形式,不然插到code字符串中會打亂排版 buffered.append(repr(string)) flush_output() for var_name in self.all_vars - self.loop_vars: vars_code.add_line(f’{var_name} = context['{var_name}']’) self.code.end_func() def _variable(self, name: str, vars_set: set) -> None: # 當(dāng)解析html過程中出現(xiàn)變量,就調(diào)用這個函數(shù) # 一方面檢驗(yàn)變量名是否合法,一方面記下變量名 if not re.match(self.valid_name_regex, name): self._syntax_error(’Not a valid name’, name) vars_set.add(name) def _syntax_error(self, message: str, thing: str) -> None: raise TemplateSyntaxError(f’{message}: {thing}’) # 這個Error類直接繼承Exception就行 def render(self, context=None) -> str: render_context = dict(self.context) if context: render_context.update(context) return self.code.get_globals()[’render’](render_context)

首先, 我們實(shí)例化了一個CodeBuilder對象作為容器使用. 在這之后, 我們定義了all_vars和loop_vars兩個集合, 并在CodeBuilder生成的函數(shù)開頭插了一個子容器. 這樣做的目的是, 最終生成的函數(shù)應(yīng)該在開頭添加類似 var_name = context[’var_name’]之類的語句, 來提取傳入的上下文變量的值. 但是, html中有哪些需要渲染的變量, 這是在渲染之后才知道的, 所以先在開頭插入一個子容器, 并創(chuàng)建all_vars這個集合, 以便在渲染html之后把這些變量的賦值語句插進(jìn)去. loop_vars則負(fù)責(zé)存放那些由于for循環(huán)產(chǎn)生的變量, 它們不需要從上下文中提取.

然后, 我們創(chuàng)建一個bufferd列表. 由于在渲染html的過程中, 變量和html語句是不需要直接轉(zhuǎn)為python語句的, 而是應(yīng)該使用類似 append_result(xxx)這樣的形式添加到代碼中去, 所以這里使用一個bufferd列表儲存變量和html語句, 等渲染到for循環(huán)等特殊語句時, 再調(diào)用flush_output一次性把這些東西全寫入CodeBuilder中. 這樣做的好處是, 最后生成的字符串代碼可能會少幾行.

萬事具備之后, 使用正則表達(dá)式分割html文本, 然后迭代分割結(jié)果并處理就行了. 對于不同類型的字符串, 使用下面的方式來處理:

html代碼塊只要有空格和換行符之外的內(nèi)容, 就放入緩沖區(qū), 等待統(tǒng)一寫入代碼

帶的{{}}的變量只要變量合法, 就記錄下變量名, 然后和html代碼塊同樣方式處理

if條件判斷 & for循環(huán)這兩個處理方法差不多, 首先檢查語法有無錯誤, 然后提取參數(shù)將其轉(zhuǎn)化為python語句插入, 最后再增加縮進(jìn)就行了. 其中for語句還需要記錄使用的變量

end語句這條語句意味著for循環(huán)或者if判斷結(jié)束, 因此減少CodeBuilder的縮進(jìn)就行

在解析完html文本之后, 清空bufferd的數(shù)據(jù), 為字符串代碼添加變量提取和函數(shù)返回值, 這樣代碼也就完成了.

四.結(jié)束

最后, 實(shí)例化Template對象, 調(diào)用其render方法傳入上下文, 就能得到渲染的模板了:

t = Template(html)result = t.render({’title’: ’高等數(shù)學(xué)’})

以上就是如何用python寫個模板引擎的詳細(xì)內(nèi)容,更多關(guān)于python寫個模板引擎的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Python 編程
相關(guān)文章:
主站蜘蛛池模板: 断桥铝破碎机_铝合金破碎机_废铁金属破碎机-河南鑫世昌机械制造有限公司 | 南京种植牙医院【官方挂号】_南京治疗种植牙医院那个好_南京看种植牙哪里好_南京茀莱堡口腔医院 尼龙PA610树脂,尼龙PA612树脂,尼龙PA1010树脂,透明尼龙-谷骐科技【官网】 | 东莞ERP软件_广州云ERP_中山ERP_台湾工厂erp系统-广东顺景软件科技有限公司 | 聚氨酯保温钢管_聚氨酯直埋保温管道_聚氨酯发泡保温管厂家-沧州万荣防腐保温管道有限公司 | 螺钉式热电偶_便携式温度传感器_压簧式热电偶|无锡联泰仪表有限公司|首页 | 皮带式输送机械|链板式输送机|不锈钢输送机|网带输送机械设备——青岛鸿儒机械有限公司 | Pos机办理_个人商户免费POS机申请-拉卡拉办理网 | 艾乐贝拉细胞研究中心 | 国家组织工程种子细胞库华南分库 | 上海APP开发-APP制作-APP定制开发-上海APP开发制作公司-咏熠科技 | TPE_TPE热塑性弹性体_TPE原料价格_TPE材料厂家-惠州市中塑王塑胶制品公司- 中塑王塑胶制品有限公司 | 志高装潢官网-苏州老房旧房装修改造-二手房装修翻新 | 蜂蜜瓶-玻璃瓶-玻璃瓶厂-玻璃瓶生产厂家-徐州贵邦玻璃制品有限公司 | 车牌识别道闸_停车场收费系统_人脸识别考勤机_速通门闸机_充电桩厂家_中全清茂官网 | 加中寰球移民官网-美国移民公司,移民机构,移民中介,移民咨询,投资移民 | 硫化罐-胶管硫化罐-山东鑫泰鑫智能装备有限公司 | 液压油缸生产厂家-山东液压站-济南捷兴液压机电设备有限公司 | 云南丰泰挖掘机修理厂-挖掘机维修,翻新,再制造的大型企业-云南丰泰工程机械维修有限公司 | 安全光栅|射频导纳物位开关|音叉料位计|雷达液位计|两级跑偏开关|双向拉绳开关-山东卓信机械有限公司 | 动库网动库商城-体育用品专卖店:羽毛球,乒乓球拍,网球,户外装备,运动鞋,运动包,运动服饰专卖店-正品运动品网上商城动库商城网 - 动库商城 | Jaeaiot捷易科技-英伟达AI显卡模组/GPU整机服务器供应商 | 十二星座查询(性格特点分析、星座运势解读) - 玄米星座网 | 长春网站建设,五合一网站设计制作,免费优化推广-长春网站建设 | 众能联合-提供高空车_升降机_吊车_挖机等一站工程设备租赁 | 深圳市宏康仪器科技有限公司-模拟高空低压试验箱-高温防爆试验箱-温控短路试验箱【官网】 | 蔡司三坐标-影像测量机-3D扫描仪-蔡司显微镜-扫描电镜-工业CT-ZEISS授权代理商三本工业测量 | 河南砖机首页-全自动液压免烧砖机,小型砌块水泥砖机厂家[十年老厂] | 广州监控安装公司_远程监控_安防弱电工程_无线wifi覆盖_泉威安防科技 | 砂石生产线_石料生产线设备_制砂生产线设备价格_生产厂家-河南中誉鼎力智能装备有限公司 | 耐酸碱泵-自吸耐酸碱泵型号「品牌厂家」立式耐酸碱泵价格-昆山国宝过滤机有限公司首页 | 深圳成考网-深圳成人高考报名网| 防水套管|柔性防水套管|伸缩器|伸缩接头|传力接头-河南伟创管道 防水套管_柔性防水套管_刚性防水套管-巩义市润达管道设备制造有限公司 | 大通天成企业资质代办_承装修试电力设施许可证_增值电信业务经营许可证_无人机运营合格证_广播电视节目制作许可证 | 壹作文_中小学生优秀满分作文大全 | 健康管理师报考条件,考试时间,报名入口—首页 | 光泽度计_测量显微镜_苏州压力仪_苏州扭力板手维修-苏州日升精密仪器有限公司 | 锂电叉车,电动叉车_厂家-山东博峻智能科技有限公司 | 天津电机维修|水泵维修-天津晟佳机电设备有限公司 | 氢氧化钙设备, 氢氧化钙生产线-淄博惠琛工贸有限公司 | 耐破强度测试仪-纸箱破裂强度试验机-济南三泉中石单品站 | 化妆品加工厂-化妆品加工-化妆品代加工-面膜加工-广东欧泉生化科技有限公司 | 电液推杆生产厂家|电动推杆|液压推杆-扬州唯升机械有限公司 |