造轮子
喜欢造轮子的我,那肯定也是实现过一套mvc的web框架的人,不止一套。 先前是用gevent做wsgi,jinja2做的模板,peewee做orm,我做了一个控制器来衔接各个组件, 如果想使用gevent的协程处理每个request_handler需要做一些妥协,比如第三方库的选择。
完成这一步之后,我觉得我不过瘾,索性直接自己写模板,自己写个易懂的orm,最后自己实现了一个类似tornado的异步框架,仿照Werkzeug来解析http协议,每个请求通过epoll做io调度,代理里存在各种的callback,各种的代码分段,各种的销魂….
上个月跟同事张磊大哥一起去参加雨痕的 《如何构建高性能web框架》的分享收益很多。 趁着这个热乎劲写写如何实现一个python的web框架.
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。 http://xiaorui.cc/?p=3176
雨痕这次虽然没有过多说明mvc的实现细节,更多的是阐述了web框架的组件的选择,轮子要怎么造,轮子的深度怎么控制. 我虽然在线上都使用过的自己开发的web框架,但依旧觉得实现过程有些low ! 这次分享收获很大,可惜的是当时没有录像啥的.
正题开始:
咱们先把web框架的模板实现给整明白,这在mvc里是重要的一环。 话说 python下的template模板还是不少的,独立的有mako,jinja2,另外像tornado,bottle,django自己也实现了一套模板。python模板大多数用在html网页中。 其实也可以来实现动态配置啥的。
怎么实现python的模板,换句话说大多数模板都咋实现的?
我这里借鉴下别人的template代码,这代码的精简干练,要比django好讲解。
自定义模板的关键字
任何一个模板都有模板的关键字及模板语法, 我们这里的 {{ var }} {% if var %} {% endif %} {% for item in items %} 这样都是关键字. python中各个模板使用方法大同小异,没啥本质区别。
关键字解析
关键字的用途是用来拆解数据的,主要是通过关键词抽取tab,我们要把这些tab转换成python语句. 这里说下解析抽取的语法.
#xiaorui.cc #html模板的内容 self.raw_text = """ <h1>{{ title }} counter: {{ 1+10 }}</h1> <p>{% for item in items %}\n{{ item }}{% endfor %}</p> """ self.re_tokens = re.compile(r'''( (?:\{\{ .*? \}\}) |(?:\{\# .*? \#\}) |(?:\{% .*? %\}) )''', re.X) tokens = self.re_tokens.split(self.raw_text)
#xiaorui.cc ['<h1>', '{{ title }}', ' counter: ', '{{ 1+10 }}', '</h1>\n <p>', '{% for item in items %}', '\n', '{{ item }}', '', '{% endfor %}', '</p>\n ']
生成Python代码
我们拿到解析关键字的字符串后,还需要通过这些关键字来构建成python语句. 这里有个CodeBuilder类,这个类是控制python代码缩进的.
#xiaorui.cc # 变量 self.re_variable = re.compile(r'\{\{ .*? \}\}') # 注释 self.re_comment = re.compile(r'\{# .*? #\}') # 标签 self.re_tag = re.compile(r'\{% .*? %\}') def _parse_text(self): handlers = ( (self.re_variable.match, self._handle_variable), # {{ variable }} (self.re_tag.match, self._handle_tag), # {% tag %} (self.re_comment.match, self._handle_comment), # {# comment #} ) default_handler = self._handle_string # 普通字符串 for token in tokens: for match, handler in handlers: if match(token): handler(token) break else: default_handler(token) def _handle_variable(self, token): """处理变量""" variable = token.strip('{} ') self.buffered.append('str({})'.format(variable)) def _handle_comment(self, token): """处理注释""" pass def _handle_string(self, token): """处理字符串""" self.buffered.append('{}'.format(repr(token))) def _handle_tag(self, token): """处理标签""" self.flush_buffer() tag = token.strip('{%} ') tag_name = tag.split()[0] self._handle_statement(tag, tag_name) def _handle_statement(self, tag, tag_name): """处理 if/for""" if tag_name in ('if', 'elif', 'else', 'for'): if tag_name in ('elif', 'else'): #如果tag_name是elif,else,那么python的ident减去4个. 这样跟上面的if ,for齐平. self.code_builder.backward() self.code_builder.add_line('{}:'.format(tag)) self.code_builder.forward() #ident增加4个 elif tag_name in ('break',): self.code_builder.add_line(tag) elif tag_name in ('endif', 'endfor'): self.code_builder.backward()
['def __func_name():', ' __result = []', " __result.extend(['<h1>',str(title),' counter: ',str(1+10),'</h1>\\n <p>'])", ' for item in items:', " __result.extend(['\\n',str(item),''])", " __result.extend(['</p>\\n '])", ' return "".join(__result)']
def __func_name(): __result = [] __result.extend(['\n <h1>',str(title),' counter: ',str(1+10),'</h1>\n <p>']) for item in items: __result.extend(['\n',str(item),'']) __result.extend(['</p>\n ']) return "".join(__result)
最后生成模板解析后的格式
def render(self, context=None): """渲染模版""" namespace = {} namespace.update(self.default_context) if context: namespace.update(context) exec(str(self.code_builder), namespace) result = namespace[self.func_name]() return result
In [1]: str = """ def set(): print url return url """ In [2]: namespace = {"url":"xiaorui.cc"} In [3]: exec(str,namespace) In [4]: namespace['set']() xiaorui.cc Out[4]: 'xiaorui.cc'
总结, 这样虽然实现了模板,但是对于使用过jinja2的人来说,会发现这个自定义的模板功能显得很单薄。 没有filters自定义函数,另外安全也是个大问题,尤其我们这边使用了exec 大杀器, escape也是个有意思的话题。
下面是python template实现的全部代码:
#coding:utf-8 import re class CodeBuilder: INDENT_STEP = 4 # 每次缩进的空格数 def __init__(self, indent=0): self.indent = indent # 当前缩进 self.lines = [] # 保存一行一行生成的代码 def forward(self): """缩进前进一步""" self.indent += self.INDENT_STEP def backward(self): """缩进后退一步""" self.indent -= self.INDENT_STEP def add(self, code): self.lines.append(code) def add_line(self, code): self.lines.append(' ' * self.indent + code) print 111111,self.lines def __str__(self): """拼接所有代码行后的源码""" return '\n'.join(map(str, self.lines)) def __repr__(self): """方便调试""" return str(self) class Template: def __init__(self, raw_text, indent=0, default_context=None, func_name='__func_name', result_var='__result'): self.raw_text = raw_text self.default_context = default_context or {} self.func_name = func_name self.result_var = result_var self.code_builder = code_builder = CodeBuilder(indent=indent) self.buffered = [] # 变量 self.re_variable = re.compile(r'\{\{ .*? \}\}') # 注释 self.re_comment = re.compile(r'\{# .*? #\}') # 标签 self.re_tag = re.compile(r'\{% .*? %\}') # 用于按变量,注释,标签分割模版字符串 self.re_tokens = re.compile(r'''( (?:\{\{ .*? \}\}) |(?:\{\# .*? \#\}) |(?:\{% .*? %\}) )''', re.X) # 生成 def __func_name(): code_builder.add_line('def {}():'.format(self.func_name)) code_builder.forward() # 生成 __result = [] code_builder.add_line('{} = []'.format(self.result_var)) # 解析模版 self._parse_text() self.flush_buffer() # 生成 return "".join(__result) code_builder.add_line('return "".join({})'.format(self.result_var)) code_builder.backward() def _parse_text(self): """解析模版""" tokens = self.re_tokens.split(self.raw_text) handlers = ( (self.re_variable.match, self._handle_variable), # {{ variable }} (self.re_tag.match, self._handle_tag), # {% tag %} (self.re_comment.match, self._handle_comment), # {# comment #} ) default_handler = self._handle_string for token in tokens: for match, handler in handlers: if match(token): handler(token) break else: default_handler(token) def _handle_variable(self, token): """处理变量""" variable = token.strip('{} ') self.buffered.append('str({})'.format(variable)) def _handle_comment(self, token): """处理注释""" pass def _handle_string(self, token): """处理字符串""" self.buffered.append('{}'.format(repr(token))) def _handle_tag(self, token): """处理标签""" # 将前面解析的字符串,变量写入到 code_builder 中 # 因为标签生成的代码需要新起一行 self.flush_buffer() tag = token.strip('{%} ') tag_name = tag.split()[0] self._handle_statement(tag, tag_name) def _handle_statement(self, tag, tag_name): """处理 if/for""" if tag_name in ('if', 'elif', 'else', 'for'): # elif 和 else 之前需要向后缩进一步 if tag_name in ('elif', 'else'): self.code_builder.backward() # if True:, elif True:, else:, for xx in yy: self.code_builder.add_line('{}:'.format(tag)) # if/for 表达式部分结束,向前缩进一步,为下一行做准备 self.code_builder.forward() elif tag_name in ('break',): self.code_builder.add_line(tag) elif tag_name in ('endif', 'endfor'): # if/for 结束,向后缩进一步 self.code_builder.backward() def flush_buffer(self): # 生成类似代码: __result.extend(['<h1>', name, '</h1>']) line = '{0}.extend([{1}])'.format( self.result_var, ','.join(self.buffered) ) self.code_builder.add_line(line) self.buffered = [] def render(self, context=None): """渲染模版""" namespace = {} namespace.update(self.default_context) if context: namespace.update(context) exec(str(self.code_builder), namespace) result = namespace[self.func_name]() return result if __name__ == "__main__": template = Template('<h1>{{ title }} {{ 1+10 }}</h1><p>{% for item in items %}\n{{ item }}{% endfor %}</p>') template.code_builder print template.render({'title': 'Python',"items":["xiaorui.cc","rfyiamcool","github.com/rfyiamcool"]})
END.
谢谢分享,雨痕有ppt么
还是没有加我啊
牛逼呀
牛呀 我看完还是不会写