mako源码解读(1)——python代码格式化
2014-12-27 21:47
429 查看
mako是一个高性能的Python模板库,他采用把模板编译成Python代码来执行的方式进行渲染,mako的git仓库是从空仓库开始,让我们分阶段一步步来看看mako是怎么做到现在这种成熟度的。
第一次commit的mako做了两件事情
分析变量
格式化输出代码
这个是目录,很少,想想现在功能强大的mako就是从这么几个文件慢慢做大的,而且过程你完全可以看到,万分鸡冻啊,感谢开源,感谢github,感谢党!感谢天朝!
test目录下有单元测试的案例
这段测试需要的代码是完成分析已声明和未声明的变量,感觉没思路啊,语法分析吗(想到最近正在学的编译原理,跪下默默的烧柱香保佑期末不挂)
我们来看看mako是怎么实现这个功能的
他竟然利用了Python标准库里的模块,直接分析得到代码当中的声明和未声明的变量,真实机(tou)智(懒),吾等来准备随便学学编译原理那套的呢(┬_┬)
其中的visitor和parse都是标准库里的,在此我们也可以看到什么样的变量可以成为已声明——被赋值的变量,未声明——必须在__builtins__模块和先前声明的变量中都没有。
来看下一个测试案例
一行一行输入,他帮我们自动进行代码格式化。
我们找到PythonPrinter这个类
in_indent_lines的值在__init__中赋值为False,让我们先看看self._flush_adjusted_lines()这个方法是什么作用。
def _flush_adjusted_lines(self):
stripspace = None
self._reset_multi_line_flags()
for entry in self.line_buffer:
if self._in_multi_line(entry):
self.stream.write(entry + "\n")
else:
entry = string.expandtabs(entry)
if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
stripspace = re.match(r"^([ \t]*)", entry).group(1)self.stream.write(self._indent_line(entry, stripspace) + "\n")
self.line_buffer = []
self._reset_multi_line_flags()
这个方法把self.line_buffer中的文本全部格式化输出了,主要是控制换行和缩进。
那这个line_buffer又是什么,让我们来全文检索一下
def print_adjusted_line(self, line):
self.in_indent_lines = False
for l in re.split(r'\r?\n', line):
self.line_buffer.append(l)
这个唯一一个他被添加元素的地方,主要是负责添加整块代码这个方法
我们先来看换行,self._in_multi_line(entry)这个方法是判断是否需要换行,让我们想想什么情况Python代码可以换行
value = \
False还有
"""我可以
跨行"""
'''我也可以
跨行'''
不是跨行取款的跨行,,,是Python代码的跨行
so~~,我们来看看mako是如何判断换行的
def _in_multi_line(self, line):
current_state = (self.backslashed or self.triplequoted)
if re.search(r"\\$", line):
self.backslashed = True
else:
self.backslashed = False
triples = len(re.findall(r"\"\"\"|\'\'\'", line))
if triples == 1 or triples % 2 != 0:
self.triplequoted = not self.triplequoted
return current_state
注意他把上次的换行记录给返回了,因为这个函数判断的是当前行是否是出于多行当中,并利用正则判断当前行的下一行是否是多行
假若是多行的话,就不用管缩进啦,所以要做个是否多行代码的判别
来看假如不是多行,也就是要严格控制缩进的那个代码分支
entry = string.expandtabs(entry)
if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
stripspace = re.match(r"^([ \t]*)", entry).group(1)self.stream.write(self._indent_line(entry, stripspace) + "\n")
self._indent_line又是什么玩意
def _indent_line(self, line, stripspace = ''):
return re.sub(r"^%s" % stripspace, self.indentstring * self.indent, line)
假若stripspce是空的情况,也就是 for entry in self.line_buffer: 这个循环还没加,也就是连第一行都没有输出的时候,
之后
stripspace现在就是我们代码块第一行前面的空格数
提取他开头的空格,咱们的_indent_line会把他替换成当前需要的缩进,也就是self.indent*4的空格数。
下面行的代码会按第一行的缩进程度相应调整
之前方法的功能是清旧账~,不能任务越积越多嘛,我们来看下面的代码
如果当前行是空的话,那么就是结束当前的代码块,联想在交互解释器中判断函数结束,确实是这样。
还有另外一种情况,某些关键词,比如else,except之类的,也需要和上层保持统一缩进,这是我们_is_unindentor方法要判断的
以上两种情况都是需要对当前缩进减一,
然后按应该的缩进输出~
输出完了,下面还有一段代码
我们还得判断下当前行是否是某些关键词,下一行需要缩进的关键词,
这样一套下来后,咱们的self.stream就是输出的整整齐齐的Python代码了
第一次commit的mako做了两件事情
分析变量
格式化输出代码
这个是目录,很少,想想现在功能强大的mako就是从这么几个文件慢慢做大的,而且过程你完全可以看到,万分鸡冻啊,感谢开源,感谢github,感谢党!感谢天朝!
test目录下有单元测试的案例
class AstParseTest(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_locate_identifiers(self): """test the location of identifiers in a python code string""" code = """ a = 10 b = 5 c = x * 5 + a + b + q (g,h,i) = (1,2,3) [u,k,j] = [4,5,6] foo.hoho.lala.bar = 7 + gah.blah + u + blah for lar in (1,2,3): gh = 5 x = 12 print "hello world, ", a, b print "Another expr", c """ parsed = ast.PythonCode(code) assert parsed.declared_identifiers == util.Set(['a','b','c', 'g', 'h', 'i', 'u', 'k', 'j', 'gh', 'lar']) assert parsed.undeclared_identifiers == util.Set(['x', 'q', 'foo', 'gah', 'blah']) parsed = ast.PythonCode("x + 5 * (y-z)") assert parsed.undeclared_identifiers == util.Set(['x', 'y', 'z']) assert parsed.declared_identifiers == util.Set()
这段测试需要的代码是完成分析已声明和未声明的变量,感觉没思路啊,语法分析吗(想到最近正在学的编译原理,跪下默默的烧柱香保佑期末不挂)
我们来看看mako是怎么实现这个功能的
class PythonCode(object): """represents information about a string containing Python code""" def __init__(self, code): self.code = code self.declared_identifiers = util.Set() self.undeclared_identifiers = util.Set() expr = parse(code, "exec") class FindIdentifiers(object): def visitAssName(s, node, *args, **kwargs): if node.name not in self.undeclared_identifiers: self.declared_identifiers.add(node.name) def vi 4000 sitName(s, node, *args, **kwargs): if node.name not in __builtins__ and node.name not in self.declared_identifiers: self.undeclared_identifiers.add(node.name) f = FindIdentifiers() visitor.walk(expr, f)
他竟然利用了Python标准库里的模块,直接分析得到代码当中的声明和未声明的变量,真实机(tou)智(懒),吾等来准备随便学学编译原理那套的呢(┬_┬)
其中的visitor和parse都是标准库里的,在此我们也可以看到什么样的变量可以成为已声明——被赋值的变量,未声明——必须在__builtins__模块和先前声明的变量中都没有。
来看下一个测试案例
def test_generate_normal(self): stream = StringIO() printer = PythonPrinter(stream) printer.print_python_line("import lala") printer.print_python_line("for x in foo:") printer.print_python_line("print x") printer.print_python_line(None) printer.print_python_line("print y") assert stream.getvalue() == \ """import lala for x in foo: print x print y """
一行一行输入,他帮我们自动进行代码格式化。
我们找到PythonPrinter这个类
import re, string class PythonPrinter(object): ...... def print_python_line(self, line, is_comment=False): """print a line of python, indenting it according to the current indent level. this also adjusts the indentation counter according to the content of the line.""" if not self.in_indent_lines: self._flush_adjusted_lines() self.in_indent_lines = True decreased_indent = False if (line is None or re.match(r"^\s*#",line) or re.match(r"^\s*$", line) ): hastext = False else: hastext = True if (not decreased_indent and not is_comment and (not hastext or self._is_unindentor(line)) ): if self.indent > 0: self.indent -=1 if len(self.indent_detail) == 0: raise "Too many whitespace closures" self.indent_detail.pop() if line is None: return self.stream.write(self._indent_line(line) + "\n") if re.search(r":[ \t]*(?:#.*)?$", line): match = re.match(r"^\s*(if|try|elif|while|for)", line) if match: indentor = match.group(1) self.indent +=1 self.indent_detail.append(indentor) else: indentor = None m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", line) if m2: self.indent += 1 self.indent_detail.append(indentor) ......
in_indent_lines的值在__init__中赋值为False,让我们先看看self._flush_adjusted_lines()这个方法是什么作用。
def _flush_adjusted_lines(self):
stripspace = None
self._reset_multi_line_flags()
for entry in self.line_buffer:
if self._in_multi_line(entry):
self.stream.write(entry + "\n")
else:
entry = string.expandtabs(entry)
if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
stripspace = re.match(r"^([ \t]*)", entry).group(1)self.stream.write(self._indent_line(entry, stripspace) + "\n")
self.line_buffer = []
self._reset_multi_line_flags()
这个方法把self.line_buffer中的文本全部格式化输出了,主要是控制换行和缩进。
那这个line_buffer又是什么,让我们来全文检索一下
def print_adjusted_line(self, line):
self.in_indent_lines = False
for l in re.split(r'\r?\n', line):
self.line_buffer.append(l)
这个唯一一个他被添加元素的地方,主要是负责添加整块代码这个方法
我们先来看换行,self._in_multi_line(entry)这个方法是判断是否需要换行,让我们想想什么情况Python代码可以换行
value = \
False还有
"""我可以
跨行"""
'''我也可以
跨行'''
不是跨行取款的跨行,,,是Python代码的跨行
so~~,我们来看看mako是如何判断换行的
def _in_multi_line(self, line):
current_state = (self.backslashed or self.triplequoted)
if re.search(r"\\$", line):
self.backslashed = True
else:
self.backslashed = False
triples = len(re.findall(r"\"\"\"|\'\'\'", line))
if triples == 1 or triples % 2 != 0:
self.triplequoted = not self.triplequoted
return current_state
注意他把上次的换行记录给返回了,因为这个函数判断的是当前行是否是出于多行当中,并利用正则判断当前行的下一行是否是多行
假若是多行的话,就不用管缩进啦,所以要做个是否多行代码的判别
来看假如不是多行,也就是要严格控制缩进的那个代码分支
entry = string.expandtabs(entry)
if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
stripspace = re.match(r"^([ \t]*)", entry).group(1)self.stream.write(self._indent_line(entry, stripspace) + "\n")
self._indent_line又是什么玩意
def _indent_line(self, line, stripspace = ''):
return re.sub(r"^%s" % stripspace, self.indentstring * self.indent, line)
假若stripspce是空的情况,也就是 for entry in self.line_buffer: 这个循环还没加,也就是连第一行都没有输出的时候,
stripspace = re.match(r"^([ \t]*)", entry).group(1)
之后
stripspace现在就是我们代码块第一行前面的空格数
提取他开头的空格,咱们的_indent_line会把他替换成当前需要的缩进,也就是self.indent*4的空格数。
下面行的代码会按第一行的缩进程度相应调整
联想下,我们在mako使用过程中,不同的<% %>的Python代码不用刻意的控制去对齐,非常的方便,得益于此处吧
然后整个_flush_adjusted_line方法的作用就是调整整一块代码缩进到他应该在的位置之前方法的功能是清旧账~,不能任务越积越多嘛,我们来看下面的代码
if (line is None or re.match(r"^\s*#",line) or re.match(r"^\s*$", line) ): hastext = False else: hastext = True # see if this line should decrease the indentation level if (not decreased_indent and not is_comment and (not hastext or self._is_unindentor(line)) ): if self.indent > 0: self.indent -=1 # if the indent_detail stack is empty, the user # probably put extra closures - the resulting # module wont compile. if len(self.indent_detail) == 0: raise "Too many whitespace closures" self.indent_detail.pop() if line is None: return
如果当前行是空的话,那么就是结束当前的代码块,联想在交互解释器中判断函数结束,确实是这样。
还有另外一种情况,某些关键词,比如else,except之类的,也需要和上层保持统一缩进,这是我们_is_unindentor方法要判断的
def _is_unindentor(self, line): """return true if the given line is an 'unindentor', relative to the last 'indent' event received.""" if len(self.indent_detail) == 0: return False indentor = self.indent_detail[-1] if indentor is None: return False match = re.match(r"^\s*(else|elif|except|finally)", line) if not match: return False return True
以上两种情况都是需要对当前缩进减一,
然后按应该的缩进输出~
输出完了,下面还有一段代码
if re.search(r":[ \t]*(?:#.*)?$", line): match = re.match(r"^\s*(if|try|elif|while|for)", line) if match: indentor = match.group(1) self.indent +=1 self.indent_detail.append(indentor) else: indentor = None m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", line) if m2: self.indent += 1 self.indent_detail.append(indentor)
我们还得判断下当前行是否是某些关键词,下一行需要缩进的关键词,
这样一套下来后,咱们的self.stream就是输出的整整齐齐的Python代码了
相关文章推荐
- vim格式化python代码
- python 字符串格式化代码
- 基于python的Lasagne包对minist的代码解读
- Eclipse中对Python代码自动格式化!
- Eclipse中对Python代码自动格式化!
- Python压缩文件基本应用代码示例解读
- 看代码学编程之python字符串格式化
- python 时间戳与格式化时间的转化实现代码
- python 代码格式化工具:pep8ify
- Python代码格式化工具_autopep8介绍
- python 字符串格式化代码
- python字符串格式化输出及相关操作代码举例
- Python压缩文件基本应用代码示例解读
- python 时间戳与格式化时间的转化实现代码
- Python代码格式化工具YAPF详解
- vim格式化python代码
- 一段好玩的Python代码解读
- Google的Python代码格式化工具YAPF详解
- pycharm:格式化python代码
- 打造vim成为python IDE, 支持自动补全, 定义跳转, 按PEP8规范自动缩进和代码格式化