欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Python的Tornado框架中內(nèi)置的模板引擎

 更新時(shí)間:2016年07月11日 19:13:58   作者:yueguanghaidao  
模板引擎是Web開(kāi)發(fā)框架中負(fù)責(zé)前端展示的關(guān)鍵,這里我們就來(lái)以實(shí)例深入解析Python的Tornado框架中內(nèi)置的模板引擎,來(lái)學(xué)習(xí)如何編寫(xiě)Tonardo的模板.

template中的_parse方法是模板文法的解析器,而這個(gè)文件中一坨一坨的各種node以及block,就是解析結(jié)果的承載者,也就是說(shuō)在經(jīng)過(guò)parse處理過(guò)后,我們輸入的tornado的html模板就變成了各種block的集合。
這些block和node的祖宗就是這個(gè)“抽象”類(lèi), _Node,它定義了三個(gè)方法定義,其中g(shù)enerate方法是必須由子類(lèi)提供實(shí)現(xiàn)的(所以我叫它“抽象”類(lèi))。
 理論上來(lái)說(shuō),當(dāng)一個(gè)類(lèi)成為祖宗類(lèi)時(shí),必定意味著這個(gè)類(lèi)包含了一些在子類(lèi)中通用的行為,那么,從_Node暴露出來(lái)的方法來(lái)看,即所有的子類(lèi)理論上都會(huì)有如下特征:
1. 可作為容器 (each_child, find_named_blocks)
2. generate
當(dāng)然了,理想總是豐滿的,現(xiàn)實(shí)也總有那么點(diǎn)兒不對(duì)勁,對(duì)于某些子孫,它們的特征看上去不是那么靠譜,比如_Text。
 _Text這個(gè)類(lèi)只用到了generate這個(gè)方法,用于將文字(Html, JS)經(jīng)過(guò)trim后添加到輸入流中,如果調(diào)用它的each_child or find_named_blocks,當(dāng)然你能這么做,但是沒(méi)有什么意義。
 前面反復(fù)說(shuō)到_Parse方法,它返回的結(jié)果是一個(gè)_ChunkList的實(shí)例,而_ChunkList繼承與_Node。這是一個(gè)體現(xiàn)了_Node容器特點(diǎn)的類(lèi),重寫(xiě)了generate方法和each_child方法,而基本上就是依次調(diào)用容器內(nèi)所有元素的相關(guān)方法而已。
 _Nodes眾多子子孫孫中比較奇葩的是_ExtendsBlock這個(gè)類(lèi),丫什么事情都沒(méi)做(That is true),看上去像是另外一個(gè)“抽象類(lèi)”,但是居然會(huì)被_Parse初始化,用于處理Extends這個(gè)token(tornado術(shù)語(yǔ))。我就納悶了,一旦這貨被generate,難道不會(huì)拋一個(gè)異常出來(lái)木?
 真正有意思的是另外幾個(gè)方法,它們有共通的模式,用_ApplyBlock來(lái)舉例
 在_ApplyBlock中,有趣的是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) 

簡(jiǎn)單來(lái)說(shuō),這個(gè)函數(shù)做了兩件事情:
定義了一個(gè)python文件全局函數(shù)叫做applyXXX():,其中的XXX是一個(gè)整形的,自增的值,返回值是一個(gè)utf8字符串。
執(zhí)行這個(gè)applyXXX函數(shù),將此函數(shù)的輸出再作為self.method這個(gè)函數(shù)的輸入。
所以,如果一個(gè)類(lèi)似于這樣的模板

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

會(huì)得到一個(gè)類(lèi)似于如下的輸出:

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

tornado的template機(jī)制,本質(zhì)上講,就是允許開(kāi)發(fā)者已HTML + template marker的方式來(lái)編寫(xiě)視圖模板,但是在背后,tornado會(huì)把這些視圖模板通過(guò)template的處理,變成可編譯的python代碼。
 拿autumn-sea上面的代碼作為例子,比較容易理解:
 View Template

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

 處理后

_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" 

實(shí)例剖析
tornado的模板基本都在template.py這個(gè)文件中,短短800多行代碼就實(shí)現(xiàn)了基本可用的模板,讓我們慢慢揭開(kāi)她的面紗。
首先我們看看tornado是如何編譯模板的,下面是個(gè)簡(jiǎn)單的模板

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

tornado最后編譯代碼如下: 

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 

是的,你沒(méi)看錯(cuò),tornado編譯就是將之翻譯成一個(gè)個(gè)代碼塊,最后通exec傳遞我們給的參數(shù)命名空間執(zhí)行_tt_execute函數(shù)。
在我們上面的模板中包含了4種預(yù)定義的NODE節(jié)點(diǎn),_ControlBlock,_Expression,_TEXT,每種Node節(jié)點(diǎn)都有自己的生成方式。
比如說(shuō)_Expression表達(dá)式節(jié)點(diǎn),也就是我們模板中的{{name}},當(dāng)_parse解析時(shí)發(fā)現(xiàn)'{'后面還是'{'就認(rèn)為是表達(dá)式節(jié)點(diǎn),

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) 

最后生成時(shí)會(huì)調(diào)用節(jié)點(diǎn)的generate方法,self.expression就是上面的name,所以當(dāng)exec的時(shí)候就會(huì)把name的值append到內(nèi)部的列表中。
像if,for等都是控制節(jié)點(diǎn),他們的定義如下: 

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) 


控制節(jié)點(diǎn)的generate方法有點(diǎn)意義,因?yàn)閕f,for等是下一行是需要縮進(jìn)的,所以調(diào)用了with writer.indent繼續(xù)縮進(jìn)控制,可以看下
_CodeWriter的indent方法。
節(jié)點(diǎn)中比較有意思的是_ExtendsBlock,這是實(shí)現(xiàn)目標(biāo)基礎(chǔ)的節(jié)點(diǎn),

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

我們發(fā)現(xiàn)并沒(méi)有定義generate方法,那當(dāng)生成繼承節(jié)點(diǎn)時(shí)不是會(huì)報(bào)錯(cuò)嗎?讓我們看一段事例

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

當(dāng)前目錄下base.html如下:

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

 我們可以看看解析后的節(jié)點(diǎn),

2016711182740215.jpg (706×500)

由于我們繼承了base.html,所以我們的應(yīng)該以base.html的模板生成,并使用新定義的block代替base.html中的block,
這是很正常的思路,tornado也的確是這么干的,只不過(guò)處理的并不是在_ExtendsBlock。
而實(shí)在Template的_generate_python中

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 

_generate_python中調(diào)用_get_ancestors獲取當(dāng)前模板的父模板,我們看到如果當(dāng)前模板的_FILE節(jié)點(diǎn)中有_ExtendsBlock就代表有父模板并通過(guò)loader.load加載父模板,此時(shí)父模板已經(jīng)是解析過(guò)的_FILE節(jié)點(diǎn)了。所以,在上面的模板中,ancestors是[當(dāng)前模板_FILE節(jié)點(diǎn),父模板_FILE節(jié)點(diǎn)],ancestors.reverse()后其實(shí)ancestors[0]就是父模板,我們看到最后是通過(guò)ancestors[0].generate(writer)來(lái)生成代碼的。那當(dāng)前模板是如何替換父模板的block內(nèi)容呢?
看上圖,block login_name通過(guò)解析為_(kāi)NamedBlock,在_generate_python中通過(guò)調(diào)用ancestor.find_named_blocks來(lái)替換
父模板的_NamedBlock的。

for ancestor in ancestors:
    ancestor.find_named_blocks(loader, named_blocks)
ancestor其實(shí)就是_FILE節(jié)點(diǎn),find_named_blocks將遍歷_FILE節(jié)點(diǎn)中所有節(jié)點(diǎn)并調(diào)用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) 

其它節(jié)點(diǎn)find_named_blocks都沒(méi)有做什么事,_NamedBlock通過(guò)named_blocks[self.name] = self替換為當(dāng)前模板的_NamedBlock,因?yàn)閍ncestors父模板在前,當(dāng)前模板在后,所以最后使用的是當(dāng)前模板的_NamedBlock。
生成代碼后generate將在給定的命名空間中exec代碼

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等,都是通過(guò)在這里注入到模板中的,當(dāng)然還有其它的是通過(guò)
web.py 中g(shù)et_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 

我們?cè)賮?lái)看看tornado的模板是如何對(duì)UI模塊的支持的。

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

在使用module時(shí)將會(huì)生成_Module節(jié)點(diǎn) 

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

我們看到其實(shí)_Module節(jié)點(diǎn)是繼承自_Expression節(jié)點(diǎn),所以最后執(zhí)行的是_tt_modules.Entry(entry)
_tt_modules定義在web.py的RequestHandler中

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

并通過(guò)上文的get_template_namespace中注入到模板中。

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)) 

所以當(dāng)執(zhí)行_tt_modules.Entry(entry)時(shí)先訪問(wèn)_UIModuleNamespace的__getattr__,后訪問(wèn)__getitem__,最后調(diào)用
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)中entry將會(huì)傳給_ui_module內(nèi)部的render,也就是args=entry
self._active_modules[name] = module(self)此時(shí)就是實(shí)例化后的UIModule,調(diào)用render獲取渲染后的內(nèi)容

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) 

當(dāng)然如果你覺(jué)得這么做費(fèi)事,也可以使用tornado自帶的TemplateModule,它繼承自UIModule,
你可以這么用

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

在module_entry.html中可以通過(guò)set_resources引用需要的靜態(tài)文件

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

這里需要注意的是:只能在Template引用的html文件中使用set_resources函數(shù),因?yàn)閟et_resources是TemplateModule.render的內(nèi)部函數(shù) 

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) 

相關(guān)文章

  • Pytorch技法之繼承Subset類(lèi)完成自定義數(shù)據(jù)拆分

    Pytorch技法之繼承Subset類(lèi)完成自定義數(shù)據(jù)拆分

    這篇文章主要介紹了Pytorch技法之繼承Subset類(lèi)完成自定義數(shù)據(jù)拆分,下文我們介紹一些下面是加載內(nèi)置訓(xùn)練數(shù)據(jù)集的常見(jiàn)操作,需要的小伙伴可以參考一下
    2022-02-02
  • 在pycharm中設(shè)置顯示行數(shù)的方法

    在pycharm中設(shè)置顯示行數(shù)的方法

    今天小編就為大家分享一篇在pycharm中設(shè)置顯示行數(shù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • Django實(shí)現(xiàn)發(fā)送郵件找回密碼功能

    Django實(shí)現(xiàn)發(fā)送郵件找回密碼功能

    在各大網(wǎng)站上,一定都遇到過(guò)找回密碼的問(wèn)題,通常采用的方式是通過(guò)發(fā)送帶有驗(yàn)證碼的郵件進(jìn)行身份驗(yàn)證,本文將介紹通過(guò)Django實(shí)現(xiàn)郵件找回密碼功能,需要的朋友可以參考下
    2019-08-08
  • pycharm?使用conda虛擬環(huán)境的詳細(xì)配置過(guò)程

    pycharm?使用conda虛擬環(huán)境的詳細(xì)配置過(guò)程

    這篇文章主要介紹了pycharm?使用conda虛擬環(huán)境,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • python常用小腳本實(shí)例總結(jié)

    python常用小腳本實(shí)例總結(jié)

    在日常的工作中我們總會(huì)面臨到各式各樣的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于python常用小腳本的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • python用socket實(shí)現(xiàn)協(xié)議TCP長(zhǎng)連接框架

    python用socket實(shí)現(xiàn)協(xié)議TCP長(zhǎng)連接框架

    大家好,本篇文章主要講的是python用socket實(shí)現(xiàn)協(xié)議TCP長(zhǎng)連接框架,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-02-02
  • Django零基礎(chǔ)入門(mén)之運(yùn)行Django版的hello world

    Django零基礎(chǔ)入門(mén)之運(yùn)行Django版的hello world

    這篇文章主要介紹了Django零基礎(chǔ)入門(mén)之運(yùn)行Django版的hello world,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • python中第三方庫(kù)lxml庫(kù)的最新詳細(xì)安裝步驟

    python中第三方庫(kù)lxml庫(kù)的最新詳細(xì)安裝步驟

    這篇文章主要給大家介紹了關(guān)于python中第三方庫(kù)lxml庫(kù)的最新詳細(xì)安裝步驟,lxml是一種使用Python編寫(xiě)的庫(kù),可以迅速、靈活地處理 XML,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • python逆向入門(mén)教程

    python逆向入門(mén)教程

    這篇文章主要介紹了python逆向入門(mén)教程,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • 基于python編寫(xiě)監(jiān)控系統(tǒng)各項(xiàng)資源的腳本

    基于python編寫(xiě)監(jiān)控系統(tǒng)各項(xiàng)資源的腳本

    這篇文章主要為大家詳細(xì)介紹了如何編寫(xiě)一個(gè)python腳本,實(shí)現(xiàn)監(jiān)控網(wǎng)絡(luò)的流量、CPU使用率、內(nèi)存使用率和磁盤(pán)使用情況,感興趣的小伙伴可以了解下
    2023-11-11

最新評(píng)論