##language:zh ''' 含有章节索引的中文 文章模板 ''' -- limodou [<>] <> = Tomz 所提的noweb模板测试 = ''由Tomz所提的文学模板,虽然不太明白,不过还是进行了简单地测试,仍然发现了meteor存在的不足,以后会改进'' == Noweb模板原型 == 由Tomz所提,没有听说过的一个东西,原型: {{{ <<*>>= <> <> <> <> <>= def myprint(): <> print "test" <>= myprint() }}} 输出结果是: {{{ def mypirnt(): print "test" myprint() }}} 可以看出文学编程不支持python的缩进。 这个是不是比meteor的调用方式更好呢? 另外,文学编程能集成面向方面编程就更好了。 by Tomz == Noweb模板原型分析 == 从上面的noweb模板原型我们可以看出,一个模板元素的定义为: {{{ <>=换行符 多行内容 空行 }}} 因为没有更多关于noweb模板的资料因此从原型可以这样规定。其中多行的内容可以包括其它的模板元素。 再从模板引用的示例与输出结果的对比我们可以看出,在<<*>>根元素中,对每个模板元素的引用后面都有一个回车,同时每个模板元素的定义中都是以空行结尾的,这样引出模板元素如果原样处理的话,必然带一个回车符。这样加上根元素中的模板引用中的回车,会生成两个回车。这一点在我没注意前的运行结果的确就是这样。于是我做了一个特殊处理,将每个元素最后的回车符去掉,输出结果与原型的结果就相同的。当然,实际上这种引用方式过于简单。因为一个模板元素完全可以是嵌入到一段文字中的,并不一定是独立为一行。不过,在这个简单的例子中我只关心能否生成相同的结果,而并不关心实际的情况。不过原型中可能存在错误,如果细心点可以看出,<<*>>中引用的模板有一个<>,但这个模板元素其实没有定义。而且输出结果中也少了一项。不知道是写错了,还是故意的。在测试中我将其去掉了。 因为noweb的模板替换(这个例子非常简单)没有超出Meteor的处理范围,只是模板定义形式不是python的定义格式,因此需要从预处理类中派生可以生成处理noweb的预处理类,然后在模板替换时指定这个新的处理器就可以了。 == Meteor的定制模板程序 == {{{#!python from Template import * import re import StringIO class NowebPreprocess(PreprocessBase): def __init__(self): PreprocessBase.__init__(self, 'noweb', '<<', '>>') def process(self, filename): f = file(filename) re_p = re.compile('^<<(.*)>>=$', re.M) vars = {} nodes = {} line = f.readline() while line: b = re_p.search(line) if b: name = b.groups()[0] line = f.readline() s = [] while line.strip(): s.append(line) line = f.readline() s[-1] = s[-1].rstrip() vars[name] = T(''.join(s)) nodes[name] = self._get_rely_on_node(vars[name].getText()) line = f.readline() return vars, nodes def _get_rely_on_node(self, s): #search for %(name)s format, make a dict re_node = re.compile(self.getPattern()) return list(sets.Set(re_node.findall(s))) register(NowebPreprocess()) if __name__ == '__main__': template = Template() template.load('noweb.txt', 'noweb') print template.value('*') }}} 可以看出,实际对noweb的处理只是对PreprocessBase进行派生,生成了新的NowebPreprocess的处理器类,通过register将其注册到Template中。测试就很简单了: * 生成模板实例 * 装入模板数据,指定'noweb'预处理器 * 进行模板替换,提供对根元素的一个空字典 == 模板测试样例 == 这是一个测试样例: {{{ <<*>>= <> <> <> <>= def myprint(): <>= print "test" <>= myprint() }}} 这与原型有区别的就是: * 将<去掉 * 在<>和<>模板中加入了缩近空格 输出结果为: {{{ def myprint(): print "test" myprint() }}} 结果与预期的差不多。由于加入了缩近,因此就是符合Python要求的代码了。因此象Tomz所提不支持缩近应该完全是模板处理系统的原因。meteor不会随意对空格进行处理的。 == 讨论 == Meteor是一个Python模板处理系统,但它提供了相应的接口可以通过派生,重载等方法来实现一个新的模板处理。当然能过这次测试我发现Meteor还有可以改进的地方: * load()方法应该可以支持文件名,文件对象,字符串等多种方式。而现在只能处理文件名。 * value()方法中提供的字典数据,在没有数据时应可以省略或只是简单地提供一个{}就可以。而现在是至少有一个根元素的字典。 关于load()的要工作集中在自定义的preprocess上,因此只是一个测试,因此在这个测试中不作处理了。 value的修改已经完成,并进行了更新。可以下载最新的[[http://wiki.woodpecker.org.cn/moin.cgi/Meteor?action=AttachFile&do=get&target=meteor0.03.zip|meteor0.03.zip]] === 已经非常不错了 by tomz === 我说的支持python的意思就是能够自动遇到冒号时在下一行开始缩进。自动和上面的缩进对齐。因为一般的语言对缩进没有要求,所以没有缩进的处理要求。如果用其它语言编程就会非常方便。自己处理缩进也凑合能用。我试了试,感觉很不错。就不用noweb软件了。 这种格式不是遇到空行分割,而是遇到下一个变量<<某某>>分割。 这种noweb格式,dohao.org上推荐的leo编辑器支持。能够自动形成outline视图。 不知道limodou是不是能够体会到这种编程方式的好处。这是一种自顶到下的编程方式。从构思程序开始就能写程序。写出来的程序更容易懂。在这里还能加入面向方面编程的功能,就棒透了。不需要具体的实现就能够表达编程的思想。只是没有适应UML方式。不适应面向对象编程。但已经不错了。 非常感谢limodou的工作。这样,limodou也能有一个把meteor加入newedit的理由了。就是用newedit实现文学编程功能。 我把两个python程序合成了一个。如下: {{{#!python # -*- coding: mbcs -*- import re import sets import copy import types class T: def __init__(self, string): self.text = string def getText(self): return self.text #预处理器基类 class PreprocessBase: #ptype 为处理器的名字,如'py' def __init__(self, ptype, beginchars='<#', endchars='#>'): self.ptype = ptype self.beginchars = beginchars #define template var's left delimeter chars self.endchars = endchars #define template var's right delimeter chars #进行模板分析,应返回T对象的一个字典和相应的相关集 def process(self, filename): return {}, {} def getPattern(self): return r'%s(\w+)%s' % (self.beginchars, self.endchars) class PyPreprocess(PreprocessBase): def process(self, filename): mod = __import__(filename) components = filename.split('.') for comp in components[1:]: mod = getattr(mod, comp) vars = {} nodes = {} for vn in dir(mod): v = getattr(mod, vn) if hasattr(v, '__class__') and v.__class__.__name__ == 'T': vars[vn] = v nodes[vn] = self._get_rely_on_node(v.getText()) return vars, nodes #取模板元素的相关集 def _get_rely_on_node(self, s): #search for %(name)s format, make a dict re_node = re.compile(self.getPattern()) return list(sets.Set(re_node.findall(s))) class NoPreprocess(Exception): pass class CircleFound(Exception): pass #定义模板处理类 class Template: preprocess ={} def __init__(self): self.vars = {} self.nodes = {} #装入模板 def load(self, tplfile, tpltype='py'): self.pre = self.preprocess.get(tpltype, None) if not self.pre: raise NoPreprocess(), 'No proper preprocess' vars, nodes = self.pre.process(tplfile) self.vars.update(vars) self.nodes.update(nodes) #生成模板值 #values应为字典的字典。即每一个模板元素如果引用有外部的变量,那么在values中应有此模板元素的一个键。 #同时它的值应为所有外部变量的一个字典 def value(self, target, values): self.global_values = values[target] self.target = target return self._value(target, values[target]) def _value(self, target, values=None): text = self.OnReplace(target, values) if text is not None: return text nodes = self.nodes[target] if not isinstance(values, types.ListType): values = [values] s = [] for v in values: vals = {} for node in nodes: if not v.has_key(node): if node in self.vars.keys(): vals[node] = self._value(node, self.global_values.get(node, {})) else: if node in self.vars.keys(): #如果node是一个模板变量,则继续替换 vals[node] = self._value(node, v[node]) else: #说明为一个外部变量 vals[node] = v[node] s.append(self._replace(target, self.vars[target].getText(), vals)) return ''.join(s) #可以自已写处理函数 #name为模板元素的名字 #text为要替换的引用变量的名字 def OnReplace(self, name, values): return None #把一段文件本的可替换信息使用values中的变量进行替换 #text是段字符串 #values是一个对应替换信息的字典 def _replace(self, name, text, values): def dosup(matchobj, name=name, text=text, values=values): if values: result = values.get(matchobj.groups()[0], matchobj.group()) else: result = matchobj.group() return result if not text: return text return re.sub(self.pre.getPattern(), dosup, text) #注册预处理器函数,是一个处理器实例 def register(preprocess): Template.preprocess[preprocess.ptype] = preprocess """ register(PyPreprocess('py')) if __name__ == '__main__': vars = { 'program':{ 'hello':[ {'var' :'var1'}, {'var' :'var2'}, {'var' :'var3'}, ], }, } template = Template() template.load('tmp2') print template.value('program', vars) """ class NowebPreprocess(PreprocessBase): def __init__(self): PreprocessBase.__init__(self, 'noweb', '<<', '>>') def process(self, filename): f = file(filename) re_p = re.compile('^<<(.*)>>=$', re.M) vars = {} nodes = {} line = f.readline() while line: b = re_p.search(line) if b: name = b.groups()[0] line = f.readline() s = [] while line.strip(): s.append(line) line = f.readline() s[-1] = s[-1].rstrip() vars[name] = T(''.join(s)) nodes[name] = self._get_rely_on_node(vars[name].getText()) line = f.readline() return vars, nodes def _get_rely_on_node(self, s): #search for %(name)s format, make a dict re_node = re.compile(self.getPattern()) return list(sets.Set(re_node.findall(s))) register(NowebPreprocess()) if __name__ == '__main__': template = Template() template.load('noweb.txt', 'noweb') print template.value('*', {'*':{}}) }}} === 与Tomz讨论 by limodou === 遇到冒号就进行缩近则只能限定在Python程序中,但这种限定必然同时会使得对noweb模板的使用受限。当然还是可以扩展NowebPreprocess类,以接收一个输出格式串,当为'python'时就对冒号后面进行缩近。不过,这种支持仍然是有限的,因为何时不用缩近却没有一个明显的符号来限定。这时可能还需要手工设置。 关于模板元素的分割,因为只是一个测试,因此暂时不改了,你需要我把它实用化吗?如果需要我就改一下,如果只是测试的话就这样了。 面向方面的编程只是一般地了解。据我所知,它一般的作法是自动在源程序中插入一些代码,这种插入应该不需要人为作特别的努力。但因此我也不清楚到底有什么用,我只是知道面向方面的编程一般是用于记日志、作分析使用。因为没见过,实在不知道在具体的应用中会对我们平时的编程和使用有多大的帮助。如果只是记日志、作分析的话,那只是对调试,分析效率有帮助。需要多了解一些。不过,这方面的东西已经超出meteor的能力了。Python虽然也有面向方面编程的东西,不过这些东西没时间研究,而且可能还比较抽象。 另外不知为什么要把两个程序合在一起。Meteor原本就是一个通用的类,可以被别的类所继承。合在一起可能只是方便你的使用。不过,我已经提供了包含Otter和noweb测试的完整代码放在meteor的下载页面中了。 leo我没有用过,也没有体会过它有什么好处。我也对这种形式上的清晰对实际的编程的帮助不是很清楚。因为这种形式完全可以通过一些别的方法来实现: * 文档 * 在主控代码中显示程序的控制逻辑,每个控制点都是一个函数或过程,可以展开 * 加注释 另外,它如何显示控制方面的信息呢?比如:循环,判断等。不过这些也与Meteor无关了,倒是一些题外话了。呵呵。 ==== 回复 by tomz ==== 让程序判断应该如何缩进确实比较困难。方法之一是在宏变量定义中就加入缩进格式,当替换的时候保留这种缩进级别。另一种方法是用ruby的程序格式,当生成代码的时候再改为python格式。 如果limodou认为暂时不需要文学编程,就不用按照我的需求来改了。如果我需求,我会自己改,这才符合开源原则。 面向方面编程我认为很有用,它是对面向对象编程的补充和升级。让每个类只实现一个核心功能,附加功能在组装程序的时候再实现。比如websphere是j2ee的应用平台,实现了用户认证,数据操作,日志等功能,就免去了每个程序再实现一遍这个功能。但从面向方面编程来说,这种解决方案还是不彻底。因为在编程的时候还要调用这些提供的API。面向方面编程来说这些工作都可以等到组装的时候来做。编程的时候只要实现自己的核心功能就可以了。这样功能实现的推后实现,保证了代码的更高的重用性。 比如在我的程序中有很多判断用户名和加减积分的操作,这样的操作就完全可以在组装的时候实现,而不是在类的代码中插入。这样才能让代码更少冗余,更清晰。再比如dtml中的html-header,html-footer,都是冗余代码。比如我编写的一些cad命令都加入了commandstart和commandend。这些其实都可以用面向方面编程来实现。 面向方面编程的具体实现就是可以用一个语句来说明在某个包中的所有类的方法调用前后插入一段代码(用通配符来表示),还可以用布尔运算来更改范围。jspect是在虚机伪码阶段实现的,我们可以在编码之前的文学编程阶段实现。 把两个文件合并只是为了我的方便。也为了和我一样对文学编程感兴趣的人提供方便。 leo只是一个outline编辑器,我感觉outline编辑功能很有用。你的newedit应该也已经实现outline功能了。 文学编程的意义和UML的意义是一样的,都是在具体编码之前对程序实现的一个描述工具。将程序设计和程序编码分开的意义我认为很大。 * 这样修改更灵活。每次修改的意义也更明确。 * 程序设计在编码之前就可以提交大家讨论,并且程序设计所用的表达方式更容易让人理解。 * 两者分开还可以促使编程工作的分工,架构师和程序员的分工。同时,也是将编程分成了两个阶段。甚至多个阶段。 * 如果程序设计很详细,甚至行数超过了具体的代码实现的行数。那么程序完全可以方便的用任何编程语言实现。(这种方式和伪码还不同,更高一个层次,但可以和伪码结合) * 文学编程实现宏替换,宏替换比函数能够应用的范围要多得多。 文档、函数部分实现了程序的结构清晰。但和文学编程完全不同,文学编程推迟了具体编码的工作,而是先展开多层次的抽象编程。两者的区别是先编码还是先写文档的区别。如果是大程序,先写文档很有必要。 另外,文学编程号称程序写好了,文档也就写好了。不过我没试过。noweb的能力就是同时生成两份文件,一份是程序代码,一部分是tex或html格式的文档。 如果使用文学编程和面向对象编程,你对drpython的苦恼:实现一个功能需要修改多处的苦恼可能会部分消失。 ==== 回复 by limodou ==== 对于你所说的面向方面编程大概意思我已经明白。的确,我也了解jaspect是在中间代码层实现的,因为没有见过具体的例子,于是对它所实现的功能不太清楚。实现这种编程对原类是否需要做一个特殊的处理,还是根本不用考虑,需要时对原类不用任何改动就可以直接插入相应的代码。我认为,要实现好的面向方面编程,在编程中应该是预留一些接口,这样才可以让后来实现的面向方面编程可以访问到原类的内部的东西,否则实现的功能是很有限的。 现在NewEdit中有Mixin功能,是在运行时对类进行组装。不过,我感觉与你所说的面向方面或别人所说的面向方面编程来是有区别。Mixin也是对原类的扩充,也就是我以前写过的文章中介绍的分布类编程。在这个wiki中的个人信息中有我关于Mixin的介绍。[[http://wiki.woodpecker.org.cn/moin.cgi/limodou#head-8acb4f1a53ad0e32454267c61a794bb3188be2c6|Mixin介绍]]不过对面向方面还是理解模糊,因此也无法仔细进行比较,等以后有机会多了解面向方面编程的东西再说吧。 文学编程是个很有意思的东西,对于我来说可能用不上。还不如直接编程序呢。只是个人喜好问题罢了。 ===== by tomz ===== AOP示例: http://www-900.ibm.com/developerWorks/cn/java/j-aspectj/ AOP概念,地位,也提到混入类。 http://www.sawin.com.cn/doc/SD/Design/aop.htm aspectj只限于最小粒度为“方法”的插入。而文学编程则能对每个宏插入。应该能实现更小粒度的插入。 嗯,个人爱好不同,我在较小的项目也想用文学编程。不过还没有起步。