用 Python 编写一个模板引擎(下)

简介: 用 Python 编写一个模板引擎

构建 AST

一旦我们做好了分词,下一步就可以遍历每个片段并构建语法树了。我们使用 Node 类来作为树的节点的基类,然后创建对每一种节点类型创建子类。每个子类都必须提供 process_fragmentrender 方法。 process_fragment 用来进一步解析片段的内容并且把需要的属性存到 Node 对象上。 render 方法负责使用提供的上下文转换对应的节点内容到 HTML。

子类也可以实现 enter_scopeexit_scope 钩子方法,这两个方法不是必须的。在编译器编译期间,会调用这两个钩子函数,他们应该负责进一步的初始化和清理工作。当一个 Node 创建了一个新的作用域(scope)的时候,会调用 enter_scope,当退出作用域时,会调用 exit_scope。关于作用域,下面会讲到。

Node 基类如下:

class _Node(object):
    def __init__(self, fragment=None):
         self.children = []
         self.creates_scope = False
         self.process_fragment(fragment)
    def process_fragment(self, fragment):
         pass
    def enter_scope(self):
         pass
    def render(self, context):
         pass
    def exit_scope(self):
         pass
    def render_children(self, context, children=None):
         if children is None:
              children = self.children
         def render_child(child):
              child_html = child.render(context)
              return''if not child_html else str(child_html)
        return ''.join(map(render_child, children))

下面是变量节点的定义:

class _Variable(_Node):
    def process_fragment(self, fragment):
        self.name = fragment
    def render(self, context):
        return resolve_in_context(self.name, context)

为了确定 Node 的类型(并且进一步初始化正确的类),需要查看片段的类型和文本。文本和变量片段直接翻译成文本节点和变量节点。块片段需要一些额外的处理 —— 他们的类型是使用块命令来确定的。比如说:

{% each items %}

是一个 each 类型的块节点,因为块命令是 each。

一个节点也可以创建作用域。在编译时,我们记录当前的作用域,并且把新的节点作为作为当前作用域的子节点。一旦遇到一个正确的关闭标签,关闭当前作用域,并且从作用域栈中把当前作用域 pop 出来,使用栈顶作为新的作用域。

def compile(self):
    root = _Root()
    scope_stack = [root]
    for fragment in self.each_fragment():
        if not scope_stack:
             raise TemplateError('nesting issues')
        parent_scope = scope_stack[-1]
        if fragment.type == CLOSE_BLOCK_FRAGMENT:
            parent_scope.exit_scope()
            scope_stack.pop()
            continue
        new_node = self.create_node(fragment)
        if new_node:
            parent_scope.children.append(new_node)
            if new_node.creates_scope:
                scope_stack.append(new_node)
                new_node.enter_scope()
    return root

渲染

管线的最后一步就是把 AST 渲染成 HTML 了。这一步访问 AST 中的所有节点并且使用传递给模板的 context 参数调用 render 方法。在渲染过程中,render 不断地解析上下文变量的值。可以使用使用 ast.literal_eval 函数,它可以安全的执行包含了 Python 代码的字符串。

def eval_expression(expr):
    try:
        return 'literal', ast.literal_eval(expr)
    except ValueError, SyntaxError:
        return 'name', expr

如果我们使用上下文变量,而不是字面量的话,需要在上下文中搜索来找到它的值。在这里需要处理包含点的变量名以及使用两个点访问外部上下文的变量。下面是 resolve 函数,也是整个难题的最后一部分了~

def resolve(name, context):
    if name.startswith('..'):
        context = context.get('..', {}
        name = name[2:]
    try:
        for tok in name.split('.'):
            context = context[tok]
        return context
    except KeyError:
        raise TemplateContextError(name)

结论

我希望这个小小的学术联系能够让你对模板引擎是怎样工作的有一点初步的感觉。这个生产级别的代码还差得很远,但是也可以作为你开发更好的工具的基础。

你可以在 GitHub 上找到完整的代码,你也可以进一步在 Hacker News 上讨论.

目录
相关文章
|
自然语言处理 编译器 Python
用 Python 编写一个模板引擎(上)
用 Python 编写一个模板引擎
125 0
|
3月前
|
前端开发 开发者 Python
从零到一:Python Web框架中的模板引擎入门与进阶
在Web开发的广阔世界里,模板引擎是连接后端逻辑与前端展示的重要桥梁。对于Python Web开发者而言,掌握模板引擎的使用是从零到一构建动态网站或应用不可或缺的一步。本文将带你从基础入门到进阶应用,深入了解Python Web框架中的模板引擎。
49 3
|
5月前
|
前端开发 数据库 开发者
构建可维护的Web应用:Python模板引擎与ORM的协同工作
【7月更文挑战第19天】在Web开发中,可维护性至关重要。Python搭配Flask或Django框架,利用模板引擎(如Jinja2)和ORM(如SQLAlchemy或Django ORM)增强开发效率和代码质量。模板引擎桥接前后端,ORM简化数据库操作,两者协同提升可读性和可测试性。例如,Flask用Jinja2渲染动态HTML,Django通过ORM处理数据库模型。这种分离关注点的方法降低了耦合,增强了应用的可维护性。
51 1
|
5月前
|
前端开发 JavaScript 数据处理
深入Python Web开发:模板引擎的力量与最佳实践
【7月更文挑战第21天】Python Web开发中,模板引擎如Jinja2促进MVC架构的View层,分离后端数据与前端展示,提升开发效率和代码复用。选择适合的模板引擎,利用其数据注入、模板继承等特性,保持模板简洁,注重安全性,是最佳实践。例如,Jinja2允许在HTML中嵌入变量并处理循环,简化渲染过程。
54 0
|
5月前
|
前端开发 开发者 Python
从零到一:Python Web框架中的模板引擎入门与进阶
【7月更文挑战第20天】模板引擎如Jinja2在Python Web开发中连接后端与前端,提高代码可读性和协作效率。Flask默认集成Jinja2,提供条件语句、循环、宏和模板继承等功能。例如,创建一个简单Flask应用,渲染"Hello, World!",并展示如何使用条件语句和循环处理数据。通过宏定义重用代码,模板继承实现页面结构共享。学习模板引擎能提升开发效率和项目质量。
69 0
|
5月前
|
SQL 前端开发 数据库
Python Web开发进阶之路:从模板引擎到ORM的全面掌握
【7月更文挑战第19天】在Python Web开发中,提升技能的关键是理解和运用模板引擎(如Jinja2)与ORM技术。模板引擎,如在Flask中使用的Jinja2,使前端HTML与后端逻辑分离,便于维护。例如,通过路由函数`show_posts()`和`render_template()`呈现文章列表模板,用Jinja2的`for`循环展示内容。ORM,如SQLAlchemy,提供Pythonic的数据库操作,将表映射为类,SQL转化为方法调用。在博客系统中,定义Post模型并与数据库交互,展示了ORM简化数据管理的优势。通过实践这些工具,开发者能更好地驾驭复杂的Web项目。
60 0
|
7月前
|
XML 缓存 API
Python 模板引擎 Jinja2 的安装和使用
Python 模板引擎 Jinja2 的安装和使用
225 0
|
XML 数据格式 Python
Python Flask 编程 | 连载 05 - Jinja2 模板引擎
Python Flask 编程 | 连载 05 - Jinja2 模板引擎
Python Flask 编程 | 连载 05 - Jinja2 模板引擎
|
Python
Python:正则re.sub实现简易的模板引擎
Python:正则re.sub实现简易的模板引擎
113 0
带你读《Python Flask Web开发入门与项目实战》之三:Jinja 2模板引擎
本书从Flask框架的基础知识讲起,逐步深入到使用Flask进行Web应用开发实战。内容通俗易懂,案例丰富,实用性强,特别适合Python Web开发的入门读者和进阶读者学习,也适合PHP程序员和Java程序员等其他Web开发爱好者阅读。另外,本书可以作为相关培训机构的教材用书。