python basic programming: Tornado-depth analysis framework Python's built-in template engine

Web development framework template engine is responsible for key front-end display, here we come to examples of in-depth analytical framework Tornado Python's built-in template engine to learn how to write a template Tonardo.
_Parse method template is the template parsing grammar , while this document cook cook various node and block, that is, the analysis results bearers, which means that after treatment parse later, we entered the tornado html template becomes a collection of various block.
These block and node fathers is this "abstract" category, _Node, which defines defines three methods, which generate method must be provided implemented by a subclass (I call it "abstract" category).
In theory, when a class becomes class fathers, must mean that this class contains some common behavior in the subclass, then the method _Node exposed from the point of view that all subclasses will theoretically have the following feature:

  1. It can be used as a container (each_child, find_named_blocks)
  2. generate
    Of course, the ideal is always full, the reality also there is always something wrong, for some children and grandchildren, their characteristics seem not so tricky, such as _Text.
    _Text generate this class uses this method only for text (Html, JS) after trim added to the input stream, if you call it each_child or find_named_blocks, of course, you can do so, but does not make sense.
    Speaking repeatedly _Parse foregoing method, it returns the results of a _ChunkList instance, and inheritance and _ChunkList _Node. This is a manifestation of the characteristics of the _Node container class overrides the method and generate each_child method, and the method is basically followed all relevant elements within the container calls only.
    _Nodes many children and grandchildren of the more wonderful is _ExtendsBlock this class, the guy did not do anything (That is true), looks like another "abstract class", but actually will be _Parse initialized to handle Extends this token (tornado terminology). I wonder, once these goods are generate, do not throw an exception out of wood?
    Also interesting is the real number of methods, they have a common pattern, with _ApplyBlock exemplified
    in _ApplyBlock, the method is interesting to generate
def generate(self, writer): 
  method_name = "apply%d" % writer.apply_counter 
  writer.apply_counter += 1
  writer.write_line("def %s():" % method_name, self.line) 
  with writer.indent(): 
    writer.write_line("_buffer = []", self.line) 
    writer.write_line("_append = _buffer.append", self.line) 
    self.body.generate(writer) 
    writer.write_line("return _utf8('').join(_buffer)", self.line) 
  writer.write_line("_append(%s(%s()))" % ( 
    self.method, method_name), self.line) 

In simple terms, this function does two things:
define a global function called python file applyXXX () :, where XXX is an integer, the increment value, the return value is a utf8 string.
ApplyXXX perform this function, then output as a function of this input self.method this function.
So, if one of these templates are similar

{%apply linkify%} {{address}} {%end%} 

It will be similar to the following output:

r = applyXXX() 
r = linkify(r) 
_append(r) 

tornado's template mechanism, in essence, is to allow developers to have HTML + template marker way to write the view template, but behind, tornado will view these templates by processing the template into python code can be compiled.
Take the above code autumn-sea way of example, relatively easy to understand:
View Template

<html> 
  <head> 
    <title>{{ title }}</title> 
  </head> 
  <body> 
    hello! {{ name }} 
  </body> 
</html>

After treatment _

buffer = [] 
_buffer.append('<html>\\n<head>\\n<title>') 
  
_tmp = title 
if isinstance(_tmp, str): _buffer.append(_tmp) 
elif isinstance(_tmp, unicode): _buffer.append(_tmp.encode('utf-8')) 
else: _buffer.append(str(_tmp)) 
  
_buffer.append('</title>\\n</head>\\n<body>\\n') 
_buffer.append('hello! ') 
  
_tmp = name 
if isinstance(_tmp, str): _buffer.append(_tmp) 
elif isinstance(_tmp, unicode): _buffer.append(_tmp.encode('utf-8')) 
else: _buffer.append(str(_tmp)) 
  
_buffer.append('\\n</body>\\n</html>\\n') 
return ''.join(_buffer)\n" 

Case analysis
tornado template basically in template.py this document, just over 800 lines of code to achieve the basic template available, so we slowly uncovered her face.
First, we look at how to compile tornado template, here is a simple template

t = Template("""\ 
{%if names%} 
  {% for name in names %} 
    {{name}} 
  {%end%} 
{%else%} 
no one 
{%end%} 
""") 

The last tornado compile the code as follows:

 def _tt_execute(): # <string>:0 
  _tt_buffer = [] # <string>:0 
  _tt_append = _tt_buffer.append # <string>:0 
  if names: # <string>:1 
    _tt_append('\n  ') # <string>:2 
    for name in names: # <string>:2 
      _tt_append('\n    ') # <string>:3 
      _tt_tmp = name # <string>:3 
      if isinstance(_tt_tmp, _tt_string_types): _tt_tmp = _tt_utf8(_tt_tmp) # <string>:3 
      else: _tt_tmp = _tt_utf8(str(_tt_tmp)) # <string>:3 
      _tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp)) # <string>:3 
      _tt_append(_tt_tmp) # <string>:3 
      _tt_append('\n  ') # <string>:4 
      pass # <string>:2 
    _tt_append('\n') # <string>:5 
    pass # <string>:5 
  else: # <string>:5 
    _tt_append('\nno one\n') # <string>:7 
    pass # <string>:1 
  _tt_append('\n') # <string>:8 
  return _tt_utf8('').join(_tt_buffer) # <string>:0 

Yes, you read that right, tornado compiler is to translate it into a block of code, we finally passed through exec to execute _tt_execute parameters namespace function.
Contains four predefined NODE node, _ControlBlock, _Expression, _TEXT, each Node node has its own way on our generation templates.
For example _Expression expression node, which is our template {{name}}, found that when _parse parsing '{' followed or '{' that it is the expression node,

class _Expression(_Node): 
  def __init__(self, expression, line, raw=False): 
    self.expression = expression 
    self.line = line 
    self.raw = raw 
  
  def generate(self, writer): 
    writer.write_line("_tt_tmp = %s" % self.expression, self.line) 
    writer.write_line("if isinstance(_tt_tmp, _tt_string_types):"
             " _tt_tmp = _tt_utf8(_tt_tmp)", self.line) 
    writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line) 
    if not self.raw and writer.current_template.autoescape is not None: 
      # In python3 functions like xhtml_escape return unicode, 
      # so we have to convert to utf8 again. 
      writer.write_line("_tt_tmp = _tt_utf8(%s(_tt_tmp))" %
               writer.current_template.autoescape, self.line) 
    writer.write_line("_tt_append(_tt_tmp)", self.line) 

Will be called when the last generation methods generate nodes, self.expression is above name, so when exec when it will append the value of name to the list of internal.
Like if, for, etc. are control nodes, they are defined as follows:

 class _ControlBlock(_Node): 
  def __init__(self, statement, line, body=None): 
    self.statement = statement 
    self.line = line 
    self.body = body 
  
  def each_child(self): 
    return (self.body,) 
  
  def generate(self, writer): 
    writer.write_line("%s:" % self.statement, self.line) 
    with writer.indent(): 
      self.body.generate(writer) 
      # Just in case the body was empty 
      writer.write_line("pass", self.line)

The method of control node generate a little sense, because if, for and so is the next line is indented required, so call with writer.indent continue indent control, can look
indent method of _CodeWriter.
Node more interesting is _ExtendsBlock, which is a node-based goals,

class _ExtendsBlock(_Node): 
  def __init__(self, name): 
    self.name = name 

We found that the method does not define generate, it inherited when generating node is not being given it? Let's look at some examples

loader = Loader('.') 
t=Template("""\ 
{% extends base.html %} 
{% block login_name %}hello world! {{ name }}{% end %} 
""",loader=loader) 

Base.html current directory as follows:

<html>  
<head>  
<title>{{ title }}</title>  
</head>  
<body>  
{% block login_name %}hello! {{ name }}{% end %}  
</body>  
</html>

We can look node parsed, Here Insert Picture Description
because we inherited base.html, so we should base.html template generator, and use the newly defined block in the block instead of base.html,
this is the idea of normal, tornado indeed is so dry, but not processed in _ExtendsBlock.
And it's _generate_python in Template

def _generate_python(self, loader, compress_whitespace): 
   buffer = StringIO() 
   try: 
     # named_blocks maps from names to _NamedBlock objects 
     named_blocks = {} 
     ancestors = self._get_ancestors(loader) 
     ancestors.reverse() 
     for ancestor in ancestors: 
       ancestor.find_named_blocks(loader, named_blocks) 
     writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template, 
               compress_whitespace) 
     ancestors[0].generate(writer) 
     return buffer.getvalue() 
   finally: 
     buffer.close() 
  
 def _get_ancestors(self, loader): 
   ancestors = [self.file] 
   for chunk in self.file.body.chunks: 
     if isinstance(chunk, _ExtendsBlock): 
       if not loader: 
         raise ParseError("{% extends %} block found, but no "
                 "template loader") 
       template = loader.load(chunk.name, self.name) 
       ancestors.extend(template._get_ancestors(loader)) 
   return ancestors 

call _generate_python in _get_ancestors get the current parent template template, to see if we have _ExtendsBlock current _FILE node template has a parent on behalf of the parent template and load the template by loader.load, when the parent template is already parsed _ FILE the node. So, in the above template, ancestors are [current template _FILE node, the parent node template _FILE], ancestors.reverse (after) in fact ancestors [0] is the parent template, we are finally seeing through the ancestors [0]. generate (writer) to generate the code. That is how the current template to replace the contents of the parent template block it?
Figure fancy, block login_name in _generate_python be replaced by the ancestor.find_named_blocks by calling resolved to _NamedBlock,
_NamedBlock parent template.

for ancestor in ancestors:
    ancestor.find_named_blocks(loader, named_blocks)
ancestor其实就是_FILE节点,find_named_blocks将遍历_FILE节点中所有节点并调用find_named_blocks
  
class _NamedBlock(_Node): 
  def find_named_blocks(self, loader, named_blocks): 
    named_blocks[self.name] = self
    _Node.find_named_blocks(self, loader, named_blocks) 

Find_named_blocks not do anything other nodes, _NamedBlock by named_blocks [self.name] = self _NamedBlock replaced with the current template, ancestors because the parent template first, after the current template, the final use of the current _NamedBlock template.
After generating the code will generate code exec given namespace

def generate(self, **kwargs): 
  """Generate this template with the given arguments."""
  namespace = { 
    "escape": escape.xhtml_escape, 
    "xhtml_escape": escape.xhtml_escape, 
    "url_escape": escape.url_escape, 
    "json_encode": escape.json_encode, 
    "squeeze": escape.squeeze, 
    "linkify": escape.linkify, 
    "datetime": datetime, 
    "_tt_utf8": escape.utf8, # for internal use 
    "_tt_string_types": (unicode_type, bytes_type), 
    # __name__ and __loader__ allow the traceback mechanism to find 
    # the generated source code. 
    "__name__": self.name.replace('.', '_'), 
    "__loader__": ObjectDict(get_source=lambda name: self.code), 
  } 
  namespace.update(self.namespace) 
  namespace.update(kwargs) 
  exec_in(self.compiled, namespace) 
  execute = namespace["_tt_execute"] 
  # Clear the traceback module's cache of source data now that 
  # we've generated a new template (mainly for this module's 
  # unittests, where different tests reuse the same name). 
  linecache.clearcache() 
  return execute() 

Datetime can be used in the template, are injected through the template where, of course, there are other by
web.py injected in get_template_namespace

 def get_template_namespace(self): 
  """Returns a dictionary to be used as the default template namespace. 
 
  May be overridden by subclasses to add or modify values. 
 
  The results of this method will be combined with additional 
  defaults in the `tornado.template` module and keyword arguments 
  to `render` or `render_string`. 
  """
  namespace = dict( 
    handler=self, 
    request=self.request, 
    current_user=self.current_user, 
    locale=self.locale, 
    _=self.locale.translate, 
    static_url=self.static_url, 
    xsrf_form_html=self.xsrf_form_html, 
    reverse_url=self.reverse_url 
  ) 
  namespace.update(self.ui) 
  return namespace 

We look at how the tornado template support for UI module.

{% for entry in entries %} 
 {% module Entry(entry) %} 
{% end %}

When using the node module will generate _Module

class _Module(_Expression): 
  def __init__(self, expression, line): 
    super(_Module, self).__init__("_tt_modules." + expression, line, 
                   raw=True) 

In fact, we see _Module node is inherited from _Expression node, the final execution is _tt_modules.Entry (entry)
_tt_modules RequestHandler defined in the web.py

self.ui["_tt_modules"] = _UIModuleNamespace(self,application.ui_modules)

And injected into the template by the get_template_namespace above.

class _UIModuleNamespace(object): 
  """Lazy namespace which creates UIModule proxies bound to a handler."""
  def __init__(self, handler, ui_modules): 
    self.handler = handler 
    self.ui_modules = ui_modules 
  
  def __getitem__(self, key): 
    return self.handler._ui_module(key, self.ui_modules[key]) 
  
  def __getattr__(self, key): 
    try: 
      return self[key] 
    except KeyError as e: 
      raise AttributeError(str(e)) 

So when performing _tt_modules.Entry (entry) to access _UIModuleNamespace of __getattr__, after visiting __getitem__, the last call
handler._ui_module (key, self.ui_modules [key] ),

def _ui_module(self, name, module): 
  def render(*args, **kwargs): 
    if not hasattr(self, "_active_modules"): 
      self._active_modules = {} 
    if name not in self._active_modules: 
      self._active_modules[name] = module(self) 
    rendered = self._active_modules[name].render(*args, **kwargs) 
    return rendered 
  return render 

_tt_modules.Entry (entry) in the interior of the entry will be passed to render _ui_module, i.e. entry = args
self._active_modules [name] = Module1 (Self) at this time is, after the instantiation UIModule, calls to render the rendered content acquired

class Entry(tornado.web.UIModule): 
  def render(self, entry, show_comments=False): 
    return self.render_string( 
      "module-entry.html", entry=entry, show_comments=show_comments)

Of course, if you do feel that trouble, you can also use the tornado comes TemplateModule, it inherits from UIModule,
you can use so

{% module Template("module-entry.html", show_comments=True) %} 

You can reference static files required by the set_resources in module_entry.html

{{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }} 

It should be noted that: set_resources function can only be used in html file Template cited as set_resources is an internal function of TemplateModule.render

 class TemplateModule(UIModule): 
  """UIModule that simply renders the given template. 
  
  {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 
  but the module version gets its own namespace (with kwargs passed to 
  Template()) instead of inheriting the outer template's namespace. 
  
  Templates rendered through this module also get access to UIModule's 
  automatic javascript/css features. Simply call set_resources 
  inside the template and give it keyword arguments corresponding to 
  the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 
  Note that these resources are output once per template file, not once 
  per instantiation of the template, so they must not depend on 
  any arguments to the template. 
  """
  def __init__(self, handler): 
    super(TemplateModule, self).__init__(handler) 
    # keep resources in both a list and a dict to preserve order 
    self._resource_list = [] 
    self._resource_dict = {} 
  
  def render(self, path, **kwargs): 
    def set_resources(**kwargs): 
      if path not in self._resource_dict: 
        self._resource_list.append(kwargs) 
        self._resource_dict[path] = kwargs 
      else: 
        if self._resource_dict[path] != kwargs: 
          raise ValueError("set_resources called with different "
                   "resources for the same template") 
      return "" 
    return self.render_string(path, set_resources=set_resources, 
                 **kwargs)

Finally, we recommend a very wide python learning resource gathering, [click to enter] , here are my collection before learning experience, learning pen

Remember, there is a glimmer of corporate experience, and calmed down to zero on the basis of the actual project data, we can also below the message, not the

Understand proposed, we will study together progress

Published 60 original articles · won praise 25 · views 80000 +

Guess you like

Origin blog.csdn.net/haoxun11/article/details/105169357