8.9. 全部放在一起

到了该将迄今为止我们已经学过并用得不错的东西放在一起的时候了。我希望您专心些。

例 8.20. translate 函数, 第 1 部分


def translate(url, dialectName="chef"): 1
    import urllib                       2
    sock = urllib.urlopen(url)          3
    htmlSource = sock.read()           
    sock.close()                       
1 这个 translate 函数有一个 可选参数 dialectName,它是一个字符串,指出我们将使用的方言。一会我们就会看到它是如何使用的。
2 嘿,等一下,在这个函数中有一个 import 语句!它在 Python 中完全合法。您已经习惯了在一个程序的前面看到 import 语句,它意味着导入的模块在程序的任何地方都是可用的。但您也可以在一个函数中导入模块,这意味着导入的模块只能在函数中使用。如果您有一个只能用在一个函数中的模块,这是一个简便的方法,使您的代码更模块化。 (当发现您周末的加班已经变成了一个 800行 的艺术作品,并且决定将其分割成一打可重用的模块时,您会感谢它的。)
3 现在我们得到了给定的URL的原始资料

例 8.21. translate 函数, 第 2 部分: 奇妙而又奇妙

    parserName = "%sDialectizer" % dialectName.capitalize() 1
    parserClass = globals()[parserName]                     2
    parser = parserClass()                                  3
1 capitalize 是一个我们以前未曾见过的字符串方法;它只是将一个字符串的第一个字母变成大写,将其它的字母强制变成小写。与某个 字符串格式化合在一起使用后,我们就得到了一种方言的名字,接着将它转化为相应的方言变换器类的名字。如果 dialectName 是字符串 'chef'parserName 将是字符串 'ChefDialectizer'
2 我们有了一个字符串形式 (parserName) 的类名称,还有一个 dictionary (globals()) 形式的全局名字空间。合起来后,我们可以得到一个以前面字符串命名的类的引用。 (回想一下,类是对象,并且它们可以象其它对象一样赋值给一个变量。) 如果 parserName 是字符串 'ChefDialectizer'parserClass 将是类 ChefDialectizer
3 最后,我们拥有了一个类对象 (parserClass),接着我们想要生成这个类的一个实例。好,我们已经知道如何去做了: 象函数一样调用类。这个类保存在一个局部变量中的事实完全不会有什么影响;我们只是象函数一样调用这个局部变量,取出这个类的一个实例。如果 parserClass 是类 ChefDialectizerparser 将是类 ChefDialectizer 的一个实例。

怎么这么麻烦?毕竟只有三个 Dialectizer 类;为什么不只使用一个 case 语句? (噢,在 Python 中不存在 case 语句,但为什么不只使用一组 if 语句呢?) 理由之一是: 可扩展性。这个 translate 函数完全不用关心我们定义了多少个方言变换器类。设想一下,如果我们明天定义了一个新的 FooDialectizer 类,把 'foo' 作为 dialectName 传给 translatetranslate 也能工作。

甚至会更好,设想将 FooDialectizer 放进一个独立的模块中,使用 from module import 将其导入。我们已经知道了,这样会将它 包含在 globals() 中 ,所以不用修改 translate ,它仍然可以正确运行,尽管 FooDialectizer 位于一个独立的文件中。

现在设想一下方言的名字是从程序外面的某个地方来的,也许是从一个数据库中,或从一个表格中的用户输入的值中。您可以使用任意多的服务端 Python 脚本架构来动态地生成网页;这个函数将接收在页面请求的查询字符串中的一个 URL 和一个方言名字 (两个都是字符串) ,接着输出 “翻译” 后的网页。

最后,设想一下,使用了一种插件架构的 Dialectizer 框架。您可以将每个 Dialectizer 类放在分别放在独立的文件中,在 dialect.py 中只留下 translate 函数。假定一种统一的命名模式,这个 translate 函数能够动态地从合适的文件中导入合适的类,除了方言名字外什么都不用给出。 (虽然您还没有看过动态导入,但我保证在后面的一章中会涉及到它。) 如果要加入一种新的方言,您只要在插件目录下加入一个以合适的名字命名的文件 (象 foodialect.py,它包含了 FooDialectizer 类) 。使用方言名 'foo' 来调用这个 translate 函数,将会查找 foodialect.py 模块,导入 FooDialectizer 类,这样就行了。

例 8.22. translate 函数, 第 3 部分

    parser.feed(htmlSource) 1
    parser.close()          2
    return parser.output()  3
1 毕竟那只是假设,这个似乎会非常令人讨厌,但这个 feed 函数执行了全部的转换工作。我们拥有存在于单个字符串中的全部 HTML 源代码,所以我们只需要调用 feed 一次。然而,您可以按您的需要经常调用 feed,分析器将不停地进行分析。所以如果我们担心内存的使用 (或者我们已经知道了将要处理非常巨大的 HTML 页面) ,我们可以在一个循环中调用它,即我们读出一点 HTML 字节,就将其送进分析器。结果会是一样的。
2 因为 feed 维护着一个内部缓冲区,当您完成时,应该总是调用分析器的 close 方法 (那怕您象我们做的一样,一次就全部送出) 。否则您可能会发现,输出丢掉了最后几个字节。
3 回想一下,output 是我们在 BaseHTMLProcessor 上定义的函数,用来 将所有缓冲的输出片段连接起来 并且以单个字符串返回。

象这样,我们已经 “翻译” 了一个网页,除了给出一个 URL 和一种方言的名字外,什么都没有给出。

进一步阅读