Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Code Generation in Python — Dismantling Jinja

Code Generation in Python — Dismantling Jinja

A talk I gave at PyCon 2012 about code generation and how Jinja2 works internally.

Avatar for Armin Ronacher

Armin Ronacher

March 12, 2012
Tweet

More Decks by Armin Ronacher

Other Decks in Programming

Transcript

  1. >>> code = compile('a = 1 + 2', '<string>', 'exec')

    >>> code <code object <module> at 0x1004d5120, file "<string>", line 1> Compile
  2. >>> import ast >>> ast.parse('a = 1 + 2') <_ast.Module

    object at 0x1004fd250> >>> code = compile(_, '<string>', 'exec') AST #1
  3. AST #2 >>> n = ast.Module([ ... ast.Assign([ast.Name('a', ast.Store())], ...

    ast.BinOp(ast.Num(1), ast.Add(), ... ast.Num(2)))])) >>> ast.fix_missing_locations(n) >>> code = compile(n, '<string>', 'exec')
  4. print "<ul>" for each item in the variable seq push

    the scope print "<li>" print the value of item and escape it as necessary print "</li>" pop the scope print "</ul>" Behavior
  5. Actual: l_seq = context.resolve('seq') write(u'<ul>') for l_item in l_seq: write(u'<li>')

    write(autoescape(l_item)) write(u'</li>') write(u'</ul>')
  6. ?

  7. Low Level Code Generation 2 0 LOAD_CONST 1 (1) 3

    LOAD_CONST 2 (2) 6 BINARY_ADD 7 STORE_FAST 0 (a) a = 1 + 2
  8. ?

  9. 2 0 LOAD_CONST 0 (0) 3 STORE_NAME 0 (a) 3

    6 SETUP_LOOP 30 (to 39) 9 LOAD_NAME 1 (xrange) 12 LOAD_CONST 1 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 16 (to 38) 22 STORE_NAME 2 (x) 4 25 LOAD_NAME 0 (a) 28 LOAD_NAME 2 (x) 31 INPLACE_ADD 32 STORE_NAME 0 (a) 35 JUMP_ABSOLUTE 19 >> 38 POP_BLOCK 5 >> 39 LOAD_NAME 0 (a) 42 PRINT_ITEM 43 PRINT_NEWLINE Slower
  10. 2 0 LOAD_CONST 1 (0) 3 STORE_FAST 0 (a) 3

    6 SETUP_LOOP 30 (to 39) 9 LOAD_GLOBAL 0 (xrange) 12 LOAD_CONST 2 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 16 (to 38) 22 STORE_FAST 1 (x) 4 25 LOAD_FAST 0 (a) 28 LOAD_FAST 1 (x) 31 INPLACE_ADD 32 STORE_FAST 0 (a) 35 JUMP_ABSOLUTE 19 >> 38 POP_BLOCK 5 >> 39 LOAD_FAST 0 (a) 42 PRINT_ITEM 43 PRINT_NEWLINE Fast
  11. 2 0 LOAD_CONST 1 (0) 3 STORE_FAST 0 (a) 3

    6 SETUP_LOOP 30 (to 39) 9 LOAD_GLOBAL 0 (xrange) 12 LOAD_CONST 2 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 16 (to 38) 22 STORE_FAST 1 (x) 4 25 LOAD_FAST 0 (a) 28 LOAD_FAST 1 (x) 31 INPLACE_ADD 32 STORE_FAST 0 (a) 35 JUMP_ABSOLUTE 19 >> 38 POP_BLOCK 5 >> 39 LOAD_FAST 0 (a) 42 PRINT_ITEM 43 PRINT_NEWLINE Fast
  12. >>> def foo(): ... a = 42 ... locals()['a'] =

    23 ... return a ... >>> foo() 42 Example
  13. print "<ul>" for each item in the variable seq push

    the scope print "<li>" print the value of item and escape it as necessary print "</li>" pop the scope print "</ul>" Remember
  14. Code l_seq = context.resolve('seq') write(u'<ul>') for l_item in l_seq: t1

    = env.get_template('other.html') for event in yield_from(t1, context, {'item': l_item}) yield event write(u'</ul>')
  15. Generated def root(context): l_sequence = context.resolve('sequence') yield u'\n<ul class=navigation>\n' l_item

    = missing for l_item in l_sequence: yield u'\n <li>%s</li>' % ( escape(l_item), ) l_item = missing yield u'\n</ul>'
  16. Source <ul class=navigation> {% for item in sequence %} <li>{{

    loop.index }}: {{ item }}</li> {% endfor %} </ul>
  17. Generated def root(context): l_sequence = context.resolve('sequence') yield u'\n<ul class=navigation>\n' l_item

    = missing for l_item, l_loop in LoopContext(l_sequence): yield u'\n <li>%s: %s</li>\n' % ( escape(environment.getattr(l_loop, 'index')), escape(l_item), ) l_item = missing yield u'\n</ul>'
  18. Source <ul class=navigation> {% for item in sequence %} <li>{{

    loop.index }}: {{ item }}</li> {% endfor %} </ul> <p>Item: {{ item }}
  19. Generated def root(context): l_item = context.resolve('item') l_sequence = context.resolve('sequence') yield

    u'\n<ul class=navigation>\n' t_1 = l_item for l_item, l_loop in LoopContext(l_sequence): yield u'\n <li>%s: %s</li>\n' % ( escape(environment.getattr(l_loop, 'index')), escape(l_item), ) l_item = t_1 yield u'\n</ul>\n<p>Item: ' yield escape(l_item)
  20. Generated def root(context): parent_template = environment.get_template('layout.html', None) for name, parent_block

    in parent_template.blocks.iteritems(): context.blocks.setdefault(name, []).append(parent_block) for event in parent_template.root_render_func(context): yield event def block_body(context): if 0: yield None yield u'\n <h1>Hello World!</h1>\n' blocks = {'body': block_body}
  21. Generated def root(context): yield u'<!doctype html>\n' for event in context.blocks['body'][0](context):

    yield event def block_body(context): if 0: yield None blocks = {'body': block_body}
  22. Generated def root(context): parent_template = environment.get_template('layout.html', None) for name, parent_block

    in parent_template.blocks.iteritems(): context.blocks.setdefault(name, []).append(parent_block) for event in parent_template.root_render_func(context): yield event def block_title(context): l_super = context.super('title', block_title) yield u'Hello | ' yield escape(context.call(l_super)) blocks = {'title': block_title}
  23. … manual code generation? why Originally the only option AST

    compilation was new in 2.6 GAE traditionally did not allow it
  24. … generators instead of buffer.append() why Required for WSGI streaming

    unless greenlets are in use Downside: StopIteration :-(
  25. … map "var_x" to "l_var_x" why Reversible to debugging purposes

    Does not clash with internals see templatetk for better approach
  26. Control #1 {% autoescape false %}<h1>{{ variable }}</h1>{% endautoescape %}

    def root(context): l_variable = context.resolve('variable') t_1 = context.eval_ctx.save() context.eval_ctx.autoescape = False yield u'<h1>%s</h1>' % ( l_variable, ) context.eval_ctx.revert(t_1)
  27. Control #2 {% autoescape flag %}<h1>{{ variable }}</h1>{% endautoescape %}

    def root(context): l_variable = context.resolve('variable') l_flag = context.resolve('flag') t_1 = context.eval_ctx.save() context.eval_ctx.autoescape = l_flag yield u'%s%s%s' % ( (context.eval_ctx.autoescape and escape or to_string)((context.eval_ctx.autoescape and Markup or identity)(u'<h1>')), (context.eval_ctx.autoescape and escape or to_string)(l_variable), (context.eval_ctx.autoescape and escape or to_string)((context.eval_ctx.autoescape and Markup or identity)(u'</h1>')), ) context.eval_ctx.revert(t_1)
  28. … far does the Markup object go? how All operators

    are overloaded All string operations are safe necessary due to operator support
  29. Example >>> from markupsafe import Markup >>> Markup('<em>%s</em>') % '<insecure>'

    Markup(u'<em>&lt;insecure&gt;</em>') >>> Markup('<em>') + '<insecure>' + Markup('</em>') Markup(u'<em>&lt;insecure&gt;</em>') >>> Markup('<em>Complex&nbsp;value</em>').striptags() u'Complex\xa0value'
  30. … do unde ned values work how Configurable Replaced by

    special object By default one level of silence
  31. Example >>> from jinja2 import Undefined >>> unicode(Undefined(name='missing_var')) u'' >>>

    unicode(Undefined(name='missing_var').attribute) Traceback (most recent call last): File "<console>", line 1, in <module> UndefinedError: 'missing_var' is undefined
  32. Q&A