python基础教程笔记—即时标记(详解)
最近一直在学习python,语法部分差不多看完了,想写一写python基础教程后面的第一个项目。因为我在网上看到的别人的博客讲解都并不是特别详细,仅仅是贴一下代码,书上内容照搬一下,对于当时刚学习python的我帮助有限。
下面是自己学习过程整理的一些内容。
基础版:
基础教程上面的项目例子,都会先出一个基础的代码版本,然后根据第一个版本,进行相应的补充完善。我们先来看一下util.py这个文件。
1 #encoding:utf-8 2 #生成器,for循环时会依次返回每一行,它只在文件的最后追加了一个空行\n 3 def lines(file): 4 for line in file:yield line 5 yield '\n' 6 #生成器,for循环时会依次返回文本块组成的函数 7 def blocks(file): 8 block = [] 9 for line in lines(file): 10 if line.strip(): 11 block.append(line) 12 elif block: 13 yield ''.join(block).strip() 14 block = []
这里的两个主要内容是生成器和for...in语法。
首先我们来看lines()方法,参数是文件,然后对文件进行循环,每次读取一行文件,主意这离的yield关键字,这里代表方法是一个生成器,循环的时候到yield我们可以理解成返回一次line内容。文本读完后,yield处是一个'\n'。
blocks()方法就使用了上面的生成器,每次循环取出内容后,对line内容进行判断,如果有值,去除两边空格,添加到列表中,否则将block列表生成字符串。我们可以看出blocks也是一个生成器,他的实际功能是,从文件中,依次读取出来一个文本块。
然后是simple_markup.py文件。
1 import sys,re 2 from util import * 3 4 print '<html><head><title>hello</title></head><body>' 5 6 title = True 7 for block in blocks(sys.stdin): 8 block = re.sub(r'\*(.+?)\*',r'<em>\1</em>',block) 9 if title: 10 print '<h1>' 11 print block 12 print '</h1>' 13 title = False 14 else: 15 print '<p>' 16 print block 17 print '</p>' 18 print '</body></html>'
这里我们需要注意一下,re.sub(r'\*(.+?)\*',r'<em>\1</em>',block),他是re模块的应用,首先增则匹配到内容,然后替换式替换。
其他部分,就是判断title是否为True,若是则给h1标签,否则给p标签。
扩展版:
在上面的文件中,功能基本实现,下面的内容是功能的复杂实现。我们接着往下看,首先是handlers.py文件。
1 #encoding:utf-8 2 class Handler: 3 '调用方法的处理类' 4 5 #判断当前类是否有对应的方法,所有的话则根据提供的额外参数使用对应方法 6 def callback(self,prefix,name,*args): 7 method = getattr(self,prefix+name,None) 8 if callable(method):return method(*args) 9 10 #callback的辅助方法,前缀就是start,只需要提供方法名即可 11 def start(self,name): 12 self.callback('start_',name) 13 #前缀为end的callback辅助方法 14 def end(self,name): 15 self.callback('end_',name) 16 17 #返回方法名subsutitution 18 def sub(self,name): 19 def substitution(match): 20 result = self.callback('sub_',name,match) 21 if result is None: result = match.group(0) 22 return result 23 return substitution 24 25 class HTMLRenderer(Handler): 26 def start_document(self): 27 print '<html><head><title>title</title></head><body>' 28 def end_documrnt(self): 29 print '</body></html>' 30 def start_paragraph(self): 31 print '<p>' 32 def end_paragraph(self): 33 print '</p>' 34 def start_heading(self): 35 print '<h2>' 36 def end_heading(self): 37 print '</h2>' 38 def start_list(self): 39 print '<ul>' 40 def end_list(self): 41 print '</ul>' 42 def start_listitem(self): 43 print '<li>' 44 def end_listitem(self): 45 print '</li>' 46 def start_title(self): 47 print '<h1>' 48 def end_title(self): 49 print '</h1>' 50 def sub_emphasis(self,match): 51 return '<em>%s</em>' % match.group(1) 52 def sub_url(self,match): 53 return '<a href="%s">%s</a>' % (match.group(1),match.group(1)) 54 def sub_mail(self,match): 55 return '<a href="mailto:%s">%s</a>' % (match.group(1),match.group(1)) 56 def feed(self,data): 57 print data
先看Handler类,他有四个方法,其中重点是callback和sub。
callback:两个必须参数,一个额外参数。
getAttr()用来判断类中是否存在prefix+name的方法,若存在返回prefix+name,否则返回None。
callable()用来判断方法是否可以调用,若可以调用,则给予参数*args并且调用,*args的含义是额外参数。
start,end是包装了callback的两个方法,不细表。
sub:
目的是返回一个函数作为re.sub的替换函数,这样re.sub就不是写死的了。其中定义了一个substitution方法,实际上调用后返回的就是这个方法。他也就是我们后面re.sub中需要用到的替换函数。
细心的朋友可能会注意到,这里有一个match参数,当时在这里我费解了很久,明明没有这个参数,可是之后的调用却确实使用到了,我打印这个参数,显示的是re对象。
书上有这样一个小例子,
1 from handlers import * 2 handler = HTMLRenderer() 3 import re 4 print re.sub(r'\*(.+?)\*',handler.sub('emphasis'),'this *is* a test ') 5 #输出为'This <em>is</em> a test'
当时在这了我完全就懵逼了,因为handler.sub('emphasis')返回的明明是一个方法,但是他没有match参数啊。
然后仔细看书,书上在前面有这样一句话,re.sub函数可以将第一个函数作为第二个参数。至少笔者觉得这句话写的很奇怪,’第一个函数‘明明要写成第一个参数啊有木有。好吧,不吐槽这些。
大概意思就是,re.sub的第二个参数可以是一个函数作为替换式,替换式的参数就是re.sub的第一个参数匹配后返回的正则对象。
这下就可以看懂了,我们会去调用sub_emphasis(self,match),然后match.group(1)表示的实际上是is。关于group(1)大家去看一下,re模块的内容,在这里我就直接告诉你他的内容,就是匹配式(.+?)中的内容。
HTMLRenderer类继承了Handler类,其中主要定义了一些用来输出的方法,不细说。
再来看rules.py文件。
1 #encoding:utf-8 2 class Rule: 3 def action(self,block,handler): 4 handler.start(self.type) 5 handler.feed(block) 6 handler.end(self.type) 7 return True 8 9 class HeadingRule(Rule): 10 type = 'heading' 11 #不包含\n,也就是说并非最后一个块;长度小于70;不以冒号结尾 12 def condition(self,block): 13 return not '\n' in block and len(block) <=70 and not block[-1] == ':' 14 15 class TitleRule(HeadingRule): 16 type = 'title' 17 #只工作一次,处理第一个快,因为处理完一次之后first的值被设置为了False,所以不会再执行处理方法了 18 first = True 19 def condition(self,block): 20 if not self.first: return False 21 self.first = False 22 return HeadingRule.condition(self,block) 23 24 class ListItemRule(Rule): 25 type = 'listitem' 26 def condition(self,block): 27 return block[0] == '-' 28 def action(self,block,handler): 29 handler.start(self.type) 30 handler.feed(block[1:].strip()) 31 handler.end(self.type) 32 return True 33 34 class ListRule(ListItemRule): 35 type = 'list' 36 inside = False 37 def condition(self,block): 38 return True 39 def action(self,block,handler): 40 if not self.inside and ListItemRule.condition(self,block): 41 handler.start(self.type) 42 self.inside = True 43 elif self.inside and not ListItemRule.condition(self,block): 44 handler.end(self.type) 45 self.inside = False 46 return False 47 48 class ParagraphRule(Rule): 49 type = 'paragraph' 50 def condition(self,block): 51 return True
这里比较简单,我们先看看基类Rule,定义了两个方法,condition和action.
condition接受一个文本块作为参数,通过返回布尔值来表示文本块是否适合当前的规则。
action接受文本块和处理程序对象作为参数,用来对文本块执行操作,进行输出。
集成的类都不复杂,这里单独说一下ListRule。
这里定义了一个变量inside为True,我们可以理解这个变量的意思是—List列表开始。因为在html中List中还会包含节点,也就是这里的ListItem,所以他会在遇到一个列表项的时候触发一次,然后在最后一个列表项的时候再次触发。所以inside作为一个标志位,用来进行判断符合规则的文本块时需要执行start还是end方法。
最后一个文件,markup.py
1 #encoding:utf-8 2 import sys,re 3 from handlers import * 4 from util import * 5 from rules import * 6 7 class Parser: 8 #初始化一些属性 9 def __init__(self,handler): 10 self.handler = handler 11 self.rules = [] 12 self.filters = [] 13 #向规则列表中添加规则 14 def addRule(self,rule): 15 self.rules.append(rule) 16 #向过滤器列表中添加过滤器 17 def addFilter(self,pattern,name): 18 #创建过滤器,实际上这里return的是一个替换式 19 def filter(block,handler): 20 return re.sub(pattern,handler.sub(name),block) 21 self.filters.append(filter) 22 #对文件进行处理 23 def parse(self,file): 24 self.handler.start('document') 25 #对文件中的文本块依次执行过滤器和规则 26 for block in blocks(file): 27 for filter in self.filters: 28 block = filter(block,self.handler) 29 for rule in self.rules: 30 #判断文本块是否符合相应规则,若符合做执行规则对应的处理方法 31 if rule.condition(block): 32 last = rule.action(block,self.handler) 33 if last:break 34 self.handler.end('document') 35 36 class BasicTextParser(Parser): 37 def __init__(self,handler): 38 Parser.__init__(self,handler) 39 self.addRule(ListRule()) 40 self.addRule(ListItemRule()) 41 self.addRule(TitleRule()) 42 self.addRule(HeadingRule()) 43 self.addRule(ParagraphRule()) 44 45 self.addFilter(r'\*(.+?)\*','emphasis') 46 self.addFilter(r'(http://[\.a-zA-Z/]+)','url') 47 self.addFilter(r'([\.a-zA-Z]+@[\.a-zA-Z]+[a-zA-Z]+)','mail') 48 49 handler = HTMLRenderer() 50 parser = BasicTextParser(handler) 51 52 parser.parse(sys.stdin) 53
同样先看基类Parser,构造函数需要一个handler对象作为参数,以供全局调用,同时初始化了两个列表。
addRule和addFilter的目的是向规则和过滤器列表添加元素。
parse方法,读取文本文件,循环出每一个文本块,先通过过滤器过滤,然后执行相应规则。
我们注意,规则和按照列表依次执行的,他会判断返回值,若为False则不再对文本块执行后续规则了。
BasicTextParser类,的构造函数只是在基类的基础上增加了,向规则和过滤器列表添加具体内容的步骤。
然后初始化类,并且对文件执行parse方法,即时标记项目完成。
后记:
学习完python语法后,真正动手做的第一个项目,对初学者还是有一点难度的,尤其是整体细节上。
后续会依次写完其他几个项目的学习笔记。欢迎喜欢的朋友关注,bye!