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 是一个你以前没见过的字符串方法;它只是将字符串的第一个字母大写,并强制将其他所有字母小写。结合一些字符串格式化,你已经将方言的名称转换为相应的 Dialectizer 类的名称。如果 dialectName 是字符串 'chef',那么 parserName 将是字符串 'ChefDialectizer'
2 你有一个字符串形式的类名 (parserName),并且你有一个字典形式的全局命名空间 (globals ())。结合起来,你可以获得对字符串所命名的类的引用。(记住,类是对象,它们可以像任何其他对象一样被赋值给变量。)如果 parserName 是字符串 'ChefDialectizer',那么 parserClass 将是类 ChefDialectizer
3 最后,你有一个类对象 (parserClass),并且你想要一个该类的实例。好吧,你已经知道如何做到这一点:像函数一样调用该类。该类被存储在一个局部变量中这一事实绝对没有区别;你只需像函数一样调用该局部变量,就会弹出一个该类的实例。如果 parserClass 是类 ChefDialectizer,那么 parser 将是类 ChefDialectizer 的一个实例。

为什么要这么麻烦?毕竟,只有 3 个 Dialectizer 类;为什么不直接使用 case 语句呢?(好吧,Python 中没有 case 语句,但为什么不直接使用一系列的 if 语句呢?)一个原因是:可扩展性。translate 函数完全不知道你定义了多少个 Dialectizer 类。想象一下,如果你明天定义了一个新的 FooDialectizertranslate 将通过传递 'foo' 作为 dialectName 来工作。

更好的是,想象一下将 FooDialectizer 放在一个单独的模块中,并使用 from module import 导入它。你已经看到这把它包含在 globals() 中,所以 translate 仍然可以正常工作,即使 FooDialectizer 在一个单独的文件中。

现在想象一下,方言的名称来自程序外部的某个地方,可能是来自数据库,也可能是来自用户在表单中输入的值。你可以使用任意数量的服务器端 Python 脚本架构来动态生成网页;这个函数可以在网页请求的查询字符串中获取一个 URL 和一个方言名称(都是字符串),并输出“翻译”后的网页。

最后,想象一下一个具有插件架构的 Dialectizer 框架。你可以将每个 Dialectizer 类放在一个单独的文件中,只在 dialect.py 中保留 translate 函数。假设有一个一致的命名方案,那么 translate 函数就可以根据方言名称,从相应的文件中动态导入相应的类。(你还没有见过动态导入,但我保证在后面的章节中会介绍它。)要添加一个新的方言,你只需在插件目录中添加一个命名适当的文件(比如包含 FooDialectizer 类的 foodialect.py)。使用方言名称 '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 和一个方言名称。

扩展阅读