jinja2の簡単な原理(exec関数の使われる実例)

python製のテンプレート・エンジンjinja2がどんな処理しているのか勉強してみました。メタクラス,ジェネレーターなどpythonらしい書き方をいくつかおぼえることができました。あと、exec関数の使い方。これは、pythonらしいのか不明ですが、jinja2で具体的な使い方を学ぶことができます。これから、jinja2の処理の流れを紹介しますが、それはある意味exec関数の使い方の例の紹介になっていると思います。

jinja2はその他の処理系と同様にソースコード(テンプレート)をLexerでトークン化し、それをParserでAstNodeに変換します。それをさらにCodeGeneratorがpython codeに変換します。これをexec code in namespaceで実行してから、code中に定義されているrootという関数をnamespaceから取り出し実行することによってrenderingします。python codeを生成するところまで書くのは大変ですし、ある意味決まり切った処理ですが、python codeを与えてから、レンダリングするまでは簡単ですので、ここに簡略化したコードを書いておきます。まず、テストコードはこんな感じになります。

#!/usr/bin/env python2.6
import unittest
from test import test_support
import MY_jinja2

class TemplateTests(unittest.TestCase):
  def test_template_from_code(self):
    code = """\
from MY_jinja2.runtime import to_string
def root(dic):
    l_seq = dic['seq']
    for l_item in l_seq:
        yield to_string(l_item)
"""
    tmpl = MY_jinja2.Template.from_code(code)
    self.assertEqual("0123456789", tmpl.render(seq = range(10)))
if __name__ == '__main__':
  test_support.run_unittest(TemplateTests)

ここからがソースコード本体になります。

  • MY_jinja2/__init__.py
from MY_jinja2.environment import *
  • MY_jinja2/environment.py
class Template(object):
  @classmethod
  def from_code(cls, code):
    tpl = object.__new__(cls)
    namespace = {}
    exec code in namespace
    tpl.render_func = namespace['root']
    return tpl
  def render(self, *args, **kwargs):
    dic = dict(*args, **kwargs)
    return ''.join(self.render_func(dic))
  • MY_jinja2/runtime.py
to_string = unicode