“Beginning Python”(五)“Instant Markup 2”

来源:互联网 发布:linux进程组 编辑:程序博客网 时间:2024/06/15 20:18

    本文接着“Instant Markup 1”,从代码的角度来分析“Instant Markup”工程。

一、类图和流程

    “Instant Markup”工程的类图关系如下:

      

       注:上述类图比前一篇的更精确。

     “Instant Markup”工程的主要流程如下:

                                                


二、代码分析

    借助上面的类图和流程图,我们能够更容易把握“Instant Markup”工程代码的整体思路。下面再按照逐个具体的类来一一分析它应用的一些python编程知识和设计方法。

1,Handler

    Handler在整个工程中的作用就是:将输入的文本块,转换为某种markup文本块并输出。从类图中可以看出,为了方便程序扩展,这里采用的是“HTMLRenderer继承自Handler”的模式。这里需要转换并输出的是html文件,它实现的只是“HTMLRenderer”,如果后续要扩展支持转为XML文件,仅需再仿照“HTMLRenderer”实现一个“XMLRenderer”即可。

    再看类图中“BasicTextParser”与“HTMLRenderer”是“依赖”关系——后者通过前者的构造函数传入前者。也就是说,它们是动态的、临时的关系,因此,在运行时可以传入“HTMLRenderer”,也可以传入其他的,比如“XMLRenderer”。

注:UML类图知识 http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html

    类图里列了几个典型的“HTMLRenderer”函数:start_paragraph和end_paragraph分别添加html的段落标头符和标尾符,sub_emphasis用于将字符串转换为html的强调格式字符串,feed则按原样输出。典型代码如下:

class HTMLRenderer(Handler):    def start_paragraph(self):        print('<p>')    def end_paragraph(self):        print('</p>')    def sub_emphasis(self, match):        return '<em>{}</em>'.format(match.group(1))def sub_url(self, match):        return '<a href="{}">{}</a>'.format(match.group(1), match.group(1))    def sub_mail(self, match):        return '<a href="mailto:{}">{}</a>'.format(match.group(1), match.group(1))    def feed(self, data):        print(data)

关于上述代码中的几个sub函数,扩展说名一下:

1)它应用了str.format()函数,它的specifier是一个花括符,花括符中间可以带编号。

2)它使用了re模块中的match.group()函数,用于获取match对象的一个分组。

注:python正则表达式知识 https://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html#undefined


    现在来看基类“Handler”:

class Handler:    def callback(self, prefix, name, *args):        method = getattr(self, prefix + name, None)        if callable(method): return method(*args)    def start(self, name):        self.callback('start_', name)    def end(self, name):        self.callback('end_', name)    def sub(self, name):        def substitution(match):            result = self.callback('sub_', name, match)            if result is None: match.group(0)            return result        return substitution

    可以说:Handler模块的灵活性和可扩展性,完全是由它的基类Handler决定的。其中,重点是callback()函数。简单来说,Handler中的这套机制实现的就是:无须事先知道Handler子类中函数的全名,而是在调用的时候,通过传入字符串参数的方式,调用对应的Handler子类的某个函数。想象一下,这个机制的妙处:一般函数调用都要在代码中明确写成函数全名,也就是说,运行之前就已经固定了,在某行代码中执行某个函数的调用。而Handler的这套机制允许程序在运行期,自己根据某个规则产生一个字符串,再用这个字符串去匹配调用某个函数。明显地,延后决策极大的提升了程序的灵活性,也减少了逻辑部分代码的量。

    说完了这套机制的妙处,再来看它是怎么实现的。它主要运用了两个python编程技巧:

1)“Interface and Introspection”(接口和内省)

    在《Beginnning Python》的第7章“Advanced Abstraction: Python Class”中,有一节专门谈到了python类的接口和元编程,即“Interface and Introspection”。

    “Interface”是与“polymorphsim”相关联的概念,简单点说,你需要知道的就是该对象有这么一个接口,而不需要关系它具体是怎么实现的,或者说,不需关心这个接口在不同的类对象上的作用有什么差异。相对Java,python的接口无须特别声明,甚至,你只需要假设某个对象有这么一个接口,如果没有,程序执行会失败。

    也就是说,在调用python类的接口时,都是先假设它有这个接口,如果假设不成立,那么程序就崩溃了。有没有更好的办法处理“假设不成立”的情况呢?

    答案是,有!那就是“Introspection”!

    所谓的“Introspection”,其实就是“元编程”,它指的是程序在运行期,能获知某个对象的信息,包括:这个对象有哪些成员变量(attributes)和成员函数(methods)。QT也支持“元编程”(meta-object)。

    查看Handler.callback函数,它应用的就是python的“Introspection”机制:先用getattr函数搜索是否存在一个名为“prefix+name”的函数,如果不存在,返回默认值“None”。然后,用callable函数检查返回的函数是否为“None”。如果不是,则将参数传给它,执行真正的函数调用。

    要理解“getattr”函数,还需要知道python中“Atrributes, Functions, and Methods”这三者的区别。在《Beginnning Python》的第7章“Advanced Abstraction: Python Class”中有如下几句话,很明确的描述了这三者的关系。
   
"Functions that are bound to object attributes like this are called methods."

    “Attributes are variables that are a part of the object, just like methods; actually, methods are almost like attributes bound to functions.”

    “The self parameter (mentioned in the previous section) is, in fact, what distinguishes methods from functions. Methods (or, more technically, bound methods) have their first parameter bound to the instance they belong to, so you don’t have to supply it.”

    “While you can certainly bind an attribute to a plain function, it won’t have that special self parameter

    我在此总结一下:

    a. “Attributes”是对象的成员,一般的对象成员都是数据成员,除了数据成员外,“Attributes”也可以绑定函数,类似于C++的函数指针。因为python是动态类型语言,某个“attribute”绑定的是普通数据,它就是数据成员;某个“attribute”绑定的是函数,那它就是函数成员(不知道有没有这个称呼?)。严格意义来说,最基本的对象只有“Attributes”,当然,对象的“Attributes”也可以在运行时动态增加删除和改变。

    b. Function是类外的函数,不带self参数;Method是类内的函数,带self参数,严格来说,Method应该称为bound method,python对象创建出来的时候,会自动去绑定method和一个同名的attribute,没有未绑定的Method。当然,在运行期,也可以将这个attribute重新绑定其他函数,Function。

    c. self参数只在声明时体现,Methods被调用时,不须显示写出。


2)“Collecting Parameters”(可变参数列表)

    在《Beginnning Python》的第6章“Abstraction: Python Function”中,有一节专门谈到了python的参数:“The Magic of Parameters”。它包括以下内容:

    a. 参数传递:按值传参(拷贝)和按引用传参;

    b. 参数顺序:位置参数和关键词参数(Keywords Parameters)

    c. 默认参数

    d. 可变参数和逆过程:collecting parameters and reversing the process。

    而callback就应用了“collecting parameters”。它能够自动识别和匹配参数的个数。


    除了callback函数外,sub函数也值得分析。

    a. sub函数嵌套了定一个函数substitution。

    b. sub函数并不像start和end一样直接调用callback,而是返回一个函数(对象),并将它作为re.sub函数的参数。

例如:

>>> import re>>> re.sub(r'\*(.+?)\*', handler.sub('emphasis'), 'This *is* a test')'This <em>is</em> a test'

注:这里要熟悉re.sub函数的原型和用法,它其中一个参数可以是函数(对象)。


2,Rule

    观察类图,虽然Rule的派生类比较多,但是,Rule及其派生类都很简单,它们都只有一个属性“type”和两个接口“condition”和“action”。事实上,Rules是从Parse分离出来的功能子模块,它们可以任意增删和组合,增加parse类的灵活性。

    Rules这个功能子模块都有些什么功能呢?

1)识别输入的block,并将block分类。“condition”

2)转换block。“action”

    Rules就是这么简单,应用代码如下:

        for block in blocks(file):            for filter in self.filters:                block = filter(block, self.handler)            for rule in self.rules:                if rule.condition(block):                    last = rule.action(block,                           self.handler)                    if last: break

    从上面的代码可以看出,filter的作用与Rule类似,但是,鉴于filter过于简单,没有必要再把它也分离出来了。此外,需要注意的是:filters不是一个普通的“attribute”,它是一个函数(Function)list,在上述第3行代码中,可以看到直接将filter做函数用。而filter函数的定义是嵌套在addFilters函数里面的,如下:

    def addFilter(self, pattern, name):        def filter(block, handler):            return re.sub(pattern, handler.sub(name), block)        self.filters.append(filter)

    我们再将话题转回到Rules,这些派生类其实都很简单,condition函数基本上就是做一个条件判断,返回true or false。但是,基类Rule值得一看:

class Rule:    def action(self, block, handler):        handler.start(self.type)        handler.feed(block)        handler.end(self.type)        return True

    从上面的代码可以看出:是Rule在调用Handler,而不是Parser。

    分析完Rules和Handler,你可能有个疑问:为什么不同的Rules抽象成了不同的(派生)类,而Handler只有一个—— HTMLRenderer ?在HTMLRenderer中用不同的函数对应不同的Rules(派生)类,为什么不将这些函数分别抽象为一个个类?

    这个问题非常有意义。我们在进行OOP设计时,不能过度抽象,而要根据实际情况,该抽象到什么程度就抽象到什么程度。(class是比function更高级别的抽象)

    具体到这个工程,由于我们需要扩展的是:从HTML到XML、LATEX等Markup文本。不管是哪种markup文本,都需要先按照某些规则分割输入文本。也就是说,将Rules抽象为类,可以复用,可以任意组合。

    再来看HTMLRenderer中的各个函数,它事实上是将各个输入block转换为html风格,这种风格是html特有的,不能被XML、LATEX等复用,没有抽象成类的必要。


3,Parser

    最后看一下Parser的构造函数

class Parser:    def __init__(self, handler):        self.handler = handler        self.rules = []        self.filters = []

    很明显:

1)handler是作为构造函数的参数传进来的,它们是依赖关系。

2)rules和filters都是list。