造轮子
喜欢造轮子的我,那肯定也是实现过一套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么
还是没有加我啊
牛逼呀
牛呀 我看完还是不会写