实例故事 | 1 实例故事 Realy Taste Storys 笔者自 2000 年接触 Python 到现在,一直对 Python 的兴趣和信心有增无减。 但 Python 在中国还处于推广普及的发展阶段。回想自己的学习体会,基本 是从“不知己不知”到“不知己知”,再到“知己知”,最后“知己不 知”。具体来说是开始涉及一个全新技术领域时,不知道这个领域的任何 信息,连“不知道什么”都没有概念;后来,通过各种途径获得了部分相 关信息,但不知道自个儿已经知道了哪些实用信息。再后来,通过实践切 实掌握了领域的基础知识,明确在领域内掌握了什么;最后,掌握了领域 间的关系,明白自己究竟还有多少知识不知道。感觉只有快速达到“知己 知”的阶段,才可能事半功倍地继续学习下去。 我想把自己的这种学习体验与心得与大家分享,但怎么向那些没有摸到 Python 秉性的人们宣传这种体验呢?developerWorks 中 David Mertz 创 作的“可爱的 Python”系列也就成了本书的原型结构。(访问地址: http://www.ibm.com/developerworks/cn/linux/theme/special/index.html#python, 精巧地址:http://bit.ly/mfUT) 下面两个以解决实际问题为出发点,有剧情有人物的小白成长故事,将 Python 最可爱的方面,以小篇幅的轻松形式组织起来!希望读者可以跟随 小白轻松体验到 Pythonic。 2 | CDays“光盘故事” CDays“光盘故事” CDay−5 Python 初体验和原始需求 3 CDay−4 可用的首个 Python 脚本 9 CDay−3 通过函式进行功能化 16 CDay−2 完成核心功能 22 CDay−1 实用化中文 31 CDay0 时刻准备着!发布 41 CDay+1 优化!对自个儿的反省 46 CDay+2 界面!不应该是难事儿 54 CDay+3 优化!多线程 69 CDayN 基于 Python 的无尽探索 75 CDays“光盘故事” | 3 CDay−5 Python 初体验和原始需求 Just use it! don't learn!——只用,不学! 剧本背景 本书采用实例故事的形式来讲解 Python,所谓实例故事,就是设计一个具体情景,让代 表读者的初学者,同代表作者的行者沟通,从而完成学习过程,在过程中引导式地给读 者展示 Python 的乐趣;当然读者不一定什么都不知道,作者也可能高明不到哪里去,但 是,有个具体的事,讲起来就更有针对性一些。 好的,这就开始。依照传统说书的方式,先来个定场诗活跃一下气氛。 左咖啡,右宝石;还是灵蟒最贴心! 最贴心,不费心,用好只须听故事。 想清楚,就清楚,一切自己来动手! 要清爽,常重构!刚刚够用是王道! 下面正式开场。 人物介绍 书中会涉及两个人物,一个是小白,一个是行者,他们分别代表—— 小白: 没有或是仅有一点编程体验的好奇宝宝,想快速上手使用 Python 解决实际问题。 4 | 实例故事 行者: 啄木鸟/CPyUG 等中国活跃 Python 社区的热心 Python 用户,说话可能有些颠三倒四, 但是绝对都是好心人。 约定 下面是本书所使用的一些体例约定。 列表: 指的是邮件列表——一种仅仅通过邮件进行群体异步交流的服务形式,是比 BBS 更加 古老和有效的沟通方式。 小结: 在每日故事讲完之后会通过小结的方式,将当日故事情节中涉及的知识点和领域技术 进行集中简述,以便读者明确要点。 练习: 每日故事的最后一节内容,虽然它所包含的问题和故事内容可能没有太大关联,但是 这些问题必须使用前述涉及的知识点和领域技术才可以解决,所以特别列出,建议读者 独立进行尝试,加强对相关知识的理解。 习题解答发布在图书维基: http://wiki.woodpecker.org.cn/moin/ObpLovelyPython/LpyAttAnswerCdays 精巧地址:http://bit.ly/XzYIX 用 SVN 下载: http://openbookproject.googlecode.com/svn/trunk/LovelyPython/exercise/part1-CDays/ 事件 小白忽然厌烦了不断地下载安装、破解,却总是找不到称心软件的生活: “烦人! 什么破软件这么不好使,还要 150$!!! 我!要!自个儿写!” 邮件列表有古老的规范和格 式。访问地址: http://www.woodpecker.org. cn/share/classes/050730- CPUG/usMaillist/ 精巧地址: http://bit.ly/43WKcR CPyUG 社区有丰富的列表 资源。 访问地址: http://wiki.woodpecker.org. cn/moin/CPUGres 精巧地址: http://bit.ly/vrqUk CDays“光盘故事” | 5 发动 究竟怎么回事儿呢?小白到列表中一说,大伙这才明白,原来他买了台刻录机,于是没 日没夜地进行 eMule 的下载,才一个月刻录出来的光盘就有了上百张,结果,当他想找 回一个专辑的 MP3 时,却遍寻不着……所以他想要一种工具,不用插入光盘就可以搜索 所有光盘的内容。就这么简单的一个愿望,可是咋就找不到好用的软件呢?!这才有了上述 那一幕。 Python! OK!你们都说 Python 好用,我就来尝试一下吧! 我是菜鸟我怕谁?! 运行环境: 推荐 ActivePython,虽然此乃商业产品,却是一个有自由软件版权保证的完善的 Python 开发应用环境,关键是文档及相关模块的预设都非常齐备。在 GNU/Linux 环境中,当然 推荐使用原生的 Python.org,主流的自由操作系统发行版都内置了 Python 环境;对应的 软件仓库中都有合适的 Python 版本可以选择,安装和使用也非常方便。 好了,下载、安装吧…… Hello World! “Hello World”非常非常著名,但凡是编程语言,第一课都要玩这个例程,下面我们也 看一看 Python 的,如图 CDay−5-1 所示! 图 CDay−5-1 Hello World 示例 再展示一个类似的,但是推荐的体验环境为 iPython,如图 CDay−5-2 所示。 PCS3 “交互环境之 winpy” 含有关于 ActivePython 的 简介,网址为: http://www.activestate.com /Products/ActivePython/ ActiveState 是商业公司,但 是对自由软件支持良多。 Python.org 的网址为: http://www.python.org/ download/ 它是 Python 语言本身的大 本营。 PCS3 PCS2“交互环境之 iPython”,相关网址为: http://ipython.scipy.org/ iPython 是个融合了 N 多 Unix Shell 环境特质的 Python 交互式命令行环 境,推荐使用,你会爱上 Tab 键的。 PCS2 6 | 实例故事 图 CDay−5-2 Hello World 示例(iPython) 就是这么简单,告诉 Python 打印“Hello World!”就行了。 所以说,对于 Python,可以只用不学! 文档 丰富的文档可以安抚我们面对未知的恐惧,推荐深入阅读以下资料,但是不推荐现在就 全面阅读。 Python Tutorial——Python 教程中文版本 在线访问:http://wiki.woodpecker.org.cn/moin/March_Liu/PyTutorial 精巧地址:http://tinyurl.com/6h2q7g 这是 CPyUG(Chinese Python User Group)中国 Python 用户组的资深专家刘鑫长期维护 的一个基础文档,也是 Python 创造者 Guido.van.Rossum 唯一亲笔撰写的技术文档! A Byte Of Python——简明 Python 教程 在线访问:http://www.woodpecker.org.cn/share/doc/abyteofpython_cn/chinese/index.html 精巧地址:http://tinyurl.com/5k8pv5 这是由沈洁元翻译的一篇流传甚广的学习 Python 的小书,从初学者的角度快速说明了一 些关键知识点。原作者是印度的一位年轻的程序员,大家可以到这本书的网站直接和作 者沟通:http://www.swaroopch.com/byteofpython/。 Python 标准库的中文版 在线访问:http://www.woodpecker.org.cn/share/doc/Python/_html/ PythonStandardLib/ 精巧地址:http://tinyurl.com/5pmvkn 由“Python 江湖 QQ 群”全体成员共同完成,Python 2.0 内置所有标准模块的说明,是CDays“光盘故事” | 7 初学者开发过程中必备的参考。 ASPN——Python Reference~Activestate 公司 Python 参考资料汇总 在线访问:http://aspn.activestate.com/ASPN/Python/Reference/ 精巧地址:http://tinyurl.com/58t3sl 原始需求 安装好了 Python 环境,在行者的指点下又收集了一批资料的链接,下面小白想真正开始 软件的创造了。但是,行者又告诫道: “明晰你的问题,当问题真正得到定义时,问题已经解决了一半。因为,程序不过是将 人的思想转述为机器可以理解的操作序列而已;对于寻求快速解决问题,而不是研究问 题的小白和 Pythoner 们,精确、恰当地描述问题,就等于写好了程序框架,余下的不过 是让程序可以运行罢了。” 于是小白根据直觉将软件需求细化了一下: 不用插入光盘就可以搜索所有光盘的内容,等于说…… 要将光盘内容索引自动储存到硬盘上 要根据储存到硬盘上的光盘信息进行搜索 就这两点,也仅此两点的需求,可以如何以及怎样通过 Python 实现?小白和读者一同期 待…… 小结 作为开始,今天小白决定使用 Python 来解决光盘内容管理这一实际问题,安装了 python 环境,运行了“Hello World!”实例。 OK!轻松的开始,但是,你知道你同时也获得了免费的绝对强大的科学计算器吗? 练习 1. 计算今年是否是闰年。判断闰年条件,满足年份模 400 为 0,或者模 4 为 0 但模 100 不为 0。 8 | 实例故事 2. 利用 Python 作为科学计算器的特性,熟悉 Python 中的常用运算符,并分别求出: 1) 12*34+78−132/6 2) (12*(34+78)−132)/6、(86/40)**5,并利用 math 模块进行数学计算,分 别求出: 145/23 的余数 0.5 的 sin 和 cos 值(注意 sin 和 cos 中的参数是弧度制表示法) 提示:可通过 import math; help("math")查看 math 帮助。 3. 找出 0~100 的所有素数。 CDays“光盘故事” | 9 CDay−4 可用的首个 Python 脚本 寻找吧!不要先想着创造——Python 是自足的。 现在的需求 小白根据曾经下载过的几个类似商业软件名称,给自个儿的软件起了个名字,叫 “PyCDC”,即 Python 制作的光盘收集器(CD Collector),或者命令工具(CD Commander)。 再次确认当前的需求: 1. 将光盘内容索引储存为硬盘上的文本文件; 2. 可以根据储存到硬盘上的光盘信息进行搜索。 因为小白痛恨数据库(只能通过神奇的 SQL 语句才能调动数据库中的数据,太不直观, 太不人性化了!),所以这第一个需求是将光盘信息读取为文本文件。 文件是系统的事儿 现在首要问题是“如何读取指定光驱中的文件列表信息?” 行者仅仅给了一条批示:文件是系统的事儿。 listdir() 系统 → 操作系统→ operating system → os 模块 10 | 实例故事 呜乎!小白搜索了一大圈才弄明白什么叫“系统”,而且找到了相应的“模块”。 在《简明 Python 教程》的第 14 章“Python 标准库——os 模块”中看到一句话:os.listdir() 返回指定目录下的所有文件和目录名。 看起来可以利用这个函数,所以,创建第一个执行脚本 CDay−4-1.py: # -*- coding: utf-8 -*- import os print os.listdir("/media/cdrom0") 代码注解: 1. # -*- coding: utf-8 -*-应该像八股文一样在每个脚本的头部声明,这是个忠告 ——为了解决中文兼容问题,同时你应该选择支持 Unicode 编码的编辑器环境,保 证在运行脚本中的每个汉字都是使用 utf-8 编码过的。 2. import os 就是告诉 Python 环境,我们要使用 os 模块,依此类推,如果你想使用 任何己有的模块包,就使用“import 模块名”的形式引用。 3. 最后一行才是我们真正想做的事儿:打印光盘根路径中的所有文件和目录。 同时,小白也进一步了解了什么是脚本,为什么会有脚本。 因为 Python 程序不用编译,它是人写的程序文件,可以直接执行,就像话剧脚本一样, 所以也称 Python 程序文件为脚本。在交互式命令行环境中,固然可以立即获得执行结果, 但是在尝试过程中的各种程序代码无法被记忆下来,所以,事先写好脚本很重要,使用 运行命令“python 脚本名.py”的形式就可以反复执行所有代码行。脚本是 Python 丰富 的运行形式之一,也是最常用的一种。 上面的脚本运行结果类似图 CDay−4-1: 图 CDay−4-1 使用脚本运行 当然在 iPython 环境下,逐步运行也一样,如图 CDay−4-2 所示。 图 CDay−4-2 iPython 中运行 以上截屏输出是使用 Ubuntu 6.06 安装光盘来测试的结果,当然,小白使用进一步的目录 PCS200 “os(.stat; .path)” 中对 os 模块有进一步细节 描述。 《简明 Python 教程》的第 14 章“Python 标准库”的访问地 址:http://www.woodpecker. org.cn/share/doc/ abyteofpython_cn/chinese/ ch14s03.html 精巧地址: http://bit.ly/ 2F1XXY PCS200 PCS5“Python 脚本文件” 中进一步说明了脚本文档在 各种操作系统中的表现,以 及基础性的知识。 PCS5 CDays“光盘故事” | 11 时,os.listdir()的确可以报告指定目录的所有信息,但是,问题是如何自动地将整个 光盘中的所有文件和目录信息都“一次性地扫描”出来? 当然,自个儿写一个是可以的,不过这只是根据每级目录的信息再次不断调用 os.listdir(),将所有层次的目录信息都逐一汇报出来而已,这样做值得吗?小白的热 情是非常容易被看来很麻烦的代码实现击碎的……于是热心的无所不能的行者又提了 句:“使用 walk()”。 提示:笔者使用 Ubuntu 系统,对于文件系统的使用和小白所使用的 Windows 系统稍有 不同:Ubuntu 没有分区的概念,光盘一般会自动统一地挂接到 /media/cdrom0 目录,而小 白则可以自由地使用 G:\等类似的盘符进行替换。放心,Python 足够聪明,它是跨平台的。 须要提醒的是,Windows 中最好使用 d:\\进行分区的指定。否则,会出现类似 d:/tmp\ something 的混合输出。 walk 一阵乱搜后,发现居然有个单独的模块 os.path 是进行文件路径处理的! 再一搜索英文,发现: Note: The newer os.walk() generator supplies similar functionality and can be easier to use. 好像在我们的环境中有两个 walk():os.path.walk()和 os.walk(),是不是后一个更新, 也更好用? 按照 os-file-dir 的示例改造脚本,尝试 CDay−4-2.py: 1 # -*- coding: utf-8 -*- 2 import os 3 for root, dirs, files in os.walk('/media/cdrom0'): 4 print root,dirs,files 然后,按照脚本运行方式尝试:~$ python CDay−4-2.py... 哇!一下子输出了一大堆的 东西!看来管用,应该是将整个光盘的文件/目录信息都扫描出来了。 PCS2 “交互环境之 iPython” 详细说明了加强的交互命令 行界面 iPython 的甜美;简要 地说 iPython 是 Python 原 生交互环境的加强,追加了很 多纯正 Unix 风味的响应特 性。 PCS2 Ubuntu 是最流行的 GNU/Linux 发行版。 官方网站: http://www.ubuntu.com/ 中文官方网站: http://www.ubuntu.org.cn/ PSC200“os(.stat; path)”含 有 os 模块进一步细节,其中 os.path()函式的详细说明可 在线查阅。 访问地址: http://aspn.activestate.com/ ASPN/docs/ActivePython/2. 4/python/lib/module-os.path .html 精巧地址: http://bit.ly/2XD0k0 PCS200 PCS4“常用自省”中详细介 绍了最常用的几个 Python 内 建函式,它们可以通过自省的 方式来快速对 Python 进行查 询,寻找想要的处理支持。 PCS4 12 | 实例故事 不管三七二十一,先保存为文件! 输出成文件 小白根据行者的指引,同时也进行了搜索,了解了 file_objects 文件对象操作相关知识, 然后自己加了点儿想象就组成了以下脚本 CDay−4-3.py: 1 # -*- coding: utf-8 -*- 2 import os 3 for root, dirs, files in os.walk('/media/cdrom0'): 4 open('mycd.cdc', 'a').write(root+dirs+files) 尝试执行~$python CDay−4-3.py,如图 CDay−4-3 所示。 图 CDay−4-3 初步 CDay−4-3.py 脚本运行结果 完了!报错!……小白感觉信心又降到了冰点。“呵呵”,行者说,“不用担心,在 Python 中 想要解决 bug 是非常轻松的,因为它有完善的回溯能力。Python 可以非常精确地汇报小 白出错的时间、地点、原因,我们只要对应修订就行了。” 现在的问题是: TypeError: cannot concatenate 'str' and 'list' objects 也就是说,不能将 str 和 list 对象进行连接。这是非常直白的数据类型错误。 什么是“数据类型”?这问题可以复杂地解释,也可以简单地说。复杂的解释并不能帮 助小白理解和记忆,简单的说也不一定能加深印象。通俗地讲呢,就是计算机内存空间 中有不同的种族,好比鸡鸭同笼,长的都挺像,但是没有办法卖相同的价钱,除非都剁 碎了炸成春卷——也就是说只要发生类似数据类型时,把它们转换成相同的类型就得了! 这种方式对于其他语言来说可能会很麻烦,但是在 Python 中是再简单不过的了。因为, Python 里一切都是对象。 讨教行者后,他们给了个样例: print "%s %s %s %s" % ("字串",["数","组"],("元","组"),{'字典':123}) 如果小白有 C 编程经验立即就可以联想到这是输出格式化的技巧,使用%s 的格式化声明, 要求后面的对象以 String 字串的格式进行转换,所以脚本 CDay−4-4.py 的改造也就顺理 PCS200“os(.stat; .path)” 中有 os 模块的进一步细节 描述,其中 os-file-dir 可 以在线查阅: http://aspn.activestate.com /ASPN/docs/ActivePython/ 2.4/python/lib/os-file-dir. html 精巧地址: http://bit.ly/eJj4f PCS200 PCS200 “os(.stat; .path)” 中对 os 模块有进一步的细 节描述,其中 file_objects 可以在线查阅: http://www.woodpecker.org.cn/ diveintopython/file_handling/ file_objects.html 精巧地址: http://bit.ly/1e41SW PCS200 “Python 里一切都是对象” 是理解应用 Python 的关键 之一,详细内容请参考《万 物皆对象》: http://www.woodpecker.org. cn/diveintopython/getting_to _know_python/everything_ is_an_object.html 精巧地址: http://bit.ly/2tiwrd PCS102 “For 循环”进一 步说明了小白首次遇见的新 语法现象;这也是一切程序 中最基本的反复运行相似语 句的技巧。 PCS102 CDays“光盘故事” | 13 成章了 : 1 # -*- coding: utf-8 -*- 2 import os 3 for root, dirs, files in os.walk('/media/cdrom0'): 4 open('mycd.cdc', 'a').write("%s %s %s" % (root,dirs,files)) 代码注解: 1. 声明是 utf-8 编码文本; 2. 引入了 os 模块; 3. 使用 os.walk() 扫描光盘,并返回三个对象; 4. 使用 open()打开 mycd.cdc 文件对象,并声明成追加模式,逐行记录以上三个对象。 不过,执行没有报错,而且文件也生成了,但是为什么打不开?……,呵呵,那是另一个 问题了。 小结 通过指点,以及在文档中狂乱的搜寻,小白今天顺利地将光盘信息全部扫描出来了,并 存储成了文件! 不过仅仅 4 行代码,真正运行可用的也就两行代码,居然已经可以达到想要的软件功能 的 50%……Python 是不是很神奇? 今天小白其实已经接触到了一大批概念: 1. 模块 —— import os; 2. 内置函式 —— open(); 3. 循环 —— for ... in ...; 4. 块界定符; 5. 注释符; 6. 对象; 7. 文件对象; 8. 对象转换; 9. 格式化声明 —— %s; 10. 数据类型。 14 | 实例故事 不过它们都包含在了 4 行代码中。我们的原则是:先用后学,快速获得体验,然后寻求 理论支持,所以,先不求甚解,达到目的,然后就自在了。 练习 1. os 模块中还有哪些功能可以使用?提示:使用 dir()和 help()。 2. open() 还有哪些模式可以使用? 3. 尝试 for .. in ..循环可以对哪些数据类型进行操作? 4. 格式化声明,还有哪些格式可以进行约定? 5. 下面的写入文件模式好吗? 有改进的余地吗? 下面是 CDay−4-5.py ,它好在哪里? 1 # -*- coding: utf-8 -*- 2 import os 3 export = "" 4 for root, dirs, files in os.walk('/media/cdrom0'): 5 export+="\n %s;%s;%s" % (root,dirs,files) 6 open('mycd2.cdc', 'w').write(export) 以下的 CDay−4-6.py 又更加好在哪里? 1 # -*- coding: utf-8 -*- 2 import os 3 export = [] 4 for root, dirs, files in os.walk('/media/cdrom0'): 5 export.append("\n %s;%s;%s" % (root,dirs,files)) 6 open('mycd2.cdc', 'w').write(''.join(export)) 6. 读取文件 cdays−4-test.txt 内容,去除空行和注释行后,以行为单位进行排序,并将 结果输出为 cdays−4-result.txt。 #some words Sometimes in life, You find a special friend; Someone who changes your life just by being part of it. Someone who makes you laugh until you can't stop; Someone who makes you believe that there really is good in the world. Someone who convinces you that there really is an unlocked door just waiting for you to open it. This is Forever Friendship. 涉及知识点的进一步信息请 查阅对应的作弊条: PCS100“import”说明引入 模块的技巧 PCS205“内建函式”介绍一 些最常用的内建函式 PCS102“For 循环”介绍最 常用的循环体 PCS103“缩进”阐述了 Python 最大的特点:使用 缩进来区分语法单位 PCS104“注释”介绍了 Python 式的注释方式 PCS105“对象”介绍了 Python 中的一等公民 PCS106“文件对象”介绍了 最常用的对外交互渠道:文 件,及其处置对象 PCS107“字串格式化”介绍 了 Python 内置的字符模板 支持特性 PCS101“内建数据类型”介 绍了动态语言的最大便利: 自由类型转换的体验 PCS CDays“光盘故事” | 15 when you're down, and the world seems dark and empty, Your forever friend lifts you up in spirits and makes that dark and empty world suddenly seem bright and full. Your forever friend gets you through the hard times,the sad times,and the confused times. If you turn and walk away, Your forever friend follows, If you lose you way, Your forever friend guides you and cheers you on. Your forever friend holds your hand and tells you that everything is going to be okay. 16 | 实例故事 CDay−3 通过函式进行功能化 不断否定自己,但要坚持最初的意愿——不论战术上如何变化,千万不要忘记战略目标。 如果小白真正理解和可以自如应用前一日所讲的 4 行代码中包含的各种知识,那么,离 完成软件之日就已经不远了。 需求 首先小白根据已有的体验,对 PyCDC 的软件需求进行了进一步完善。 1. 将光盘内容索引存储为硬盘上的文本文件。 1)存储成*.cdc 的文本文件; 2)可以快速指定文件名。 2. 根据储存到硬盘上的光盘信息进行搜索。 1)可以搜索指定目录中所有*.cdc 文件。 这样一来,可以看出 PyCDC 的使用其实分为两部分。 1. 刻录光盘时,将光盘信息通过 PyCDC 存储为对应光盘标号的*.cdc 文件。 2. 使用光盘时,在 PyCDC 中搜索,确认.cdc 文件名,即光盘标号,从而针对性地读 取确切的光盘,不用遍寻所有光盘了! CDays“光盘故事” | 17 功能化 简单地讲就是将以往验证想法的代码,变成可以方便使用的功能,让它可以重复在不同 应用环境中使用。小白想象着自个儿的 PyCDC 可以像普通的命令行工具一样来使用: python pycdc.py -e mycd1-1.cdc #将光盘内容记录为 mycd1-1.cdc python pycdc.py -e cdc/mycd1-1.cdc #将光盘内容记录到 cdc 目录中的 mycd1-1.cdc python pycdc.py -d cdc -k 中国火 #搜索 cdc 目录中的光盘信息,寻找有“中国火”字样的文件或是目录,在那张光盘中 可能还有其他的功能,但是最核心的功能应该就是这 3 样。 要想达到这种效果,最直接的的方法就是函式化! 函式化 声明函式名,定义参数,然后使用缩进,将前一日摸索出来的代码包装一下,使用参数 代替原先指定的目录和文件名,请看 CDay−3-1.py。 1 # -*- coding: utf-8 -*- 2 import os 3 def cdWalker(cdrom,cdcfile): 4 export = "" 5 for root, dirs, files in os.walk(cdrom): 6 export+="\n %s;%s;%s" % (root,dirs,files) 7 open(cdcfile, 'w').write(export) 8 cdWalker('/media/cdrom0','cd1.cdc') 9 cdWalker('/media/cdrom0','cd2.cdc') 小白获得了第一个 Python 函式,并成功运行了两次,即将同张光盘的内容记录到不同的 文件中!非常 easy。 交互参数 但是如何从命令行获取输入的参数呢? 搜索或是询问后,从行者那儿又获得一个提示:print sys.argv。 那么,无畏的小白立即创建了一个将真正使用的功能脚本 pycdc-v0.1.py,并尝试加入了 最新的提示代码: PCS108“函式”进一步说明 了什么是函式,什么时候应 该将代码集成为函式等知识。 在 Python 中对象是一等公 民,函式则是实际可用脚本 中最基础的人民了。 精巧地址: http://bit.ly/vrqUk PCS108 18 | 实例故事 1 # -*- coding: utf-8 -*- 2 import os 3 print sys.argv 4 def cdWalker(cdrom,cdcfile): 5 export = "" 6 for root, dirs, files in os.walk(cdrom): 7 export+="\n %s;%s;%s" % (root,dirs,files) 8 open(cdcfile, 'w').write(export) 9 #cdWalker('/media/cdrom0','cd1.cdc') 运行结果如图 CDay−3-1 所示: 图 CDay−3-1 pycdc-v0.1.py 运行结果 OK,出错了,但是有了 os 模块的经验,瞧着 sys.argv 这么眼熟,小白猜这是个模块, 需要引入,所以微小地改动了一下代码: 1 # -*- coding: utf-8 -*- 2 import os,sys 3 print sys.argv 4 def cdWalker(cdrom,cdcfile): 5 export = "" 6 for root, dirs, files in os.walk(cdrom): 7 export+="\n %s;%s;%s" % (root,dirs,files) 8 open(cdcfile, 'w').write(export) 9 #cdWalker('/media/cdrom0','cd1.cdc') 运行结果如图 CDay−3-2 所求: 图 CDay−3-2 修改 pycdc-v0.1.py 后的运行结果 Great! 一切如愿,然后就是简单的识别参数问题了。 逻辑判断 好了,小白有了自个儿的体验:当面对不熟悉的工具时,会对应该输入什么参数一头雾 水,此时,若软件能友好智能地进行提示,就太好了…… PCS113 “交互参数”进一 步分享了有关交互参数的使 用方法,读者可以深入了解 sys 及其使用方法。 PCS113 CDays“光盘故事” | 19 要做到这一点,得对未知用户的行为进行判定,对非期待的输入进行提示,而不是由 Python 自个儿出错中断。与其他语言类似,Python 中也有 if、else 等类似逻辑判别语句, 不妨找到相关内容,照猫画虎: 1 # -*- coding: utf-8 -*- 2 import os,sys 3 CDROM = '/media/cdrom0' 4 def cdWalker(cdrom,cdcfile): 5 export = "" 6 for root, dirs, files in os.walk(cdrom): 7 export+="\n %s;%s;%s" % (root,dirs,files) 8 open(cdcfile, 'w').write(export) 9 #cdWalker('/media/cdrom0','cd1.cdc') 10 if "-e"==sys.argv[1]: 11 cdWalker(CDROM,sys.argv[2]) 12 print "记录光盘信息到 %s" % sys.argv[2] 13 else: 14 print '''PyCDC 使用方式: 15 python pycdc.py -e mycd1-1.cdc 16 #将光盘内容记录为 mycd1-1.cdc 17 ''' 代码注解: 1. 使用全局参数 CDROM 指定当前的光盘访问路径,总是/media/cdrom0; 2. 通过 if "-e"==sys.argv[1] 判定第二个参数是-e 时,使用第三个参数作输出文件 名记录光盘信息,并输出提示; 3. 通过 else 捕获所有意外情况,输出错误提示,结束脚本。 OK,看起来应该已经完成了一两项功能,但是实际运行时,结果如图 CDay−3-3 所示: 图 CDay−3-3 pycdc-v0.1.py 运行 IO 错误 尝试带目录的输出时: IOError: [Errno 2] No such file or directory: 'cdc/mycd-1.cdc' I/O——输入/出问题,当然啦,cdc 目录并不存在。 手工建立 cdc 目录后,再运行就 OK 了,所以功能设计细化为: PCS109 “系统参数”进一 步分享了有关函式参数的使 用技巧,对于 cdWalker(CDROM, sys.argv[2]) ,读者可以深入 了解其使用方法。 PCS109 PCS110 “逻辑分支”进一 步详细说明了进行条件判别 时 Python 支持的方式。 PCS110 20 | 实例故事 ~$python pycdc.py -e mycd1-1.cdc ##第 2 个参数是"-e";使用 cdWalker()将光盘内容记录为 mycd1-1.cdc ~$python pycdc.py -e cdc/mycd1-1.cdc ##第 2 个参数是"-e";而且第 3 个参数中含有目录指定; ##就将光盘内容记录到 cdc 目录中的 mycd1-1.cdc;如果 cdc 目录不存在,可以自动创建。 ~$python pycdc.py -d cdc -k 中国火 ##第 2 个参数是"-d";而且第 4 个参数是"-k"; ##就搜索 cdc 目录中的光盘信息,输出含有关键字“中国火”的文件或是目录,指出它在哪张光 盘中。 完成以上所有功能和判定部分的伪代码: if "-e"==sys.argv[1]: #判别 sys.argv[2]中是否有目录,以便进行自动创建 cdWalker(CDROM,sys.argv[2]) print "记录光盘信息到 %s" % sys.argv[2] elif "-d"==sys.argv[1]: if "-k"==sys.argv[3]: #进行文件搜索 else: print '''PyCDC 使用方式: python pycdc.py -d cdc -k 中国火 #搜索 cdc 目录中的光盘信息,寻找有“中国火”字样的文件或是目录在哪张光盘中 ''' else: print '''PyCDC 使用方式: python pycdc.py -d cdc -k 中国火 #搜索 cdc 目录中的光盘信息,寻找有“中国火”字样的文件或是目录在哪张光盘中 ''' 到这一步,小白已经开始头大了,如果功能继续追加,软件进一步友好地自动识别各种 意外情况,这棵逻辑树将会可怕地增长下去…… 小白没有耐心和信心面对如此永无止境的意外情况的判别,于是开始寻找其他解决方 案…… 小结 为了快速将已知技巧转化为可用脚本,小白接触到了以下知识: 1. sys 模块的接收交互参数; 2. 函式概念; CDays“光盘故事” | 21 3. 逻辑判定语句; 4. 伪代码技术,进行开发复杂度的估算。 练习 1. 根据 DiPy 10.6. 处理命令行参数(http://www.woodpecker.org.cn/diveintopython/ scripts_and_streams/command_line_arguments.html,精巧地址:http://bit.ly/1x5gMw), 使用 getopt.getopt()优化当前功能函式。 2. 读取某一简单索引文件 cdays−3-test.txt,其每行格式为:文档序号关键词,现须根据 这些信息将它转化为倒排索引,即统计关键词在哪些文档中,格式如下:包含该关 键词的文档数 关键词 => 文档序号。其中,原索引文件作为命令行参数传入主程序, 并设计一个 collect 函式统计“关键字↔序号”结果对,最后在主程序中输出结果至 屏幕。 cdays−3-test.txt 内容: 1 key1 2 key2 3 key1 7 key3 8 key2 10 key1 14 key2 19 key4 20 key1 30 key3 3. 在一个 8×8 国际象棋盘上,有 8 个皇后,每个皇后占一格;要求皇后间不会出现相 互“攻击”的现象,即不能有两个皇后处在同一行、同一列或同一对角线上,问共 有多少种不同的方法。 PCS200 “os(.stat;.path)” 详细说明了 os 及其包含模 块的常用函式;分享了 os 在 常见场景中的使用技巧。 PCS200 22 | 实例故事 CDay−2 完成核心功能 利用文本文件完成核心功能 没有完美的软件,够用并且容易使用的软件已经算是完美的了。 回顾需求 已经获得命令行工具样的程序的小白几乎可以脱离小白的称号了,他可以独立根据已往 经验,不断尝试出成果来,就该是大白了。 现在可以将最初的需求细化成这样: 1. 将光盘内容索引存储为硬盘上的文本文件 1) 存储成 *.cdc 的文本文件 2) 可以快速指定文件名 通过命令行调用 python pycdc.py -e mycd1-1.cdc 2. 根据储存到硬盘上的光盘信息进行搜索 1) 可以搜索指定目录中所有*.cdc 文件 通过命令行调用 python pycdc.py -d cdc -k 中国火 但是,要完成一个个看似简单,实际有 N 多情况的逻辑判定,实在是件令人沮丧的事儿, 小白吼了声:“俺就是想做个简单的命令行工具,咋就这么难呢?” 热心的行者,又出声了:“使用 cmd 吧!” CDays“光盘故事” | 23 cmd 模块 cmd : Support for line-oriented command interpreters 咦?它是专门支持命令行界面的模块?!但是几乎没有可以照抄的代码呀! 照猫画虎的重要资源 不搜不知道,一搜吓一跳!原来英文技术书已在出版社网站上公开了书中的代码样例, 比如:《Learning Python》(见图 CDay−2-1)、《Learning Python,Second Edition》。 按照大学里论文的制造模式进行相关尝试(即从已被证实正确的基础上加以改造),成功 的机会比较大! 图 CDay−2-1 Learning Python 那么,我们可以参考第一版《Leaning Python》第 9 章的例程 rolo.py。 重构当前代码 重构(refactoring), 听上去好像是很高级的编程技术,其实说穿了就是自个儿看不过去 自个儿,于是不断地将代码修改得更加合理,使之运行起来更加高效。小白的目标是快 速实现心中的功能,但是现在面对的又一个全新的模块,他想使用干净的脚本进行尝试, 所以考虑重构,于是将原先的代码整理到 cdctools.py,创建新脚本 pycdc-v0.4.py: 1 # -*- coding: utf-8 -*- 2 import sys, cmd “资源索引”章节中有完整 的在线中文文档的资源介 绍,有意的读者可以在网络 中针对性的对有兴趣的 Python 内容进一步学习。 精巧地址: http://bit.ly/vrqUk 24 | 实例故事 3 class PyCDC(cmd.Cmd): 4 def __init__(self): 5 cmd.Cmd.__init__(self) # initialize the base class 6 def help_EOF(self): 7 print "退出程序 Quits the program" 8 def do_EOF(self, line): 9 sys.exit() 10 11 def help_walk(self): 12 print "扫描光盘内容 walk cd and export into *.cdc" 13 def do_walk(self, filename): 14 if filename == "":filename = raw_input("输入 cdc 文件名:: ") 15 print "扫描光盘内容保存到:'%s'" % filename 16 17 def help_dir(self): 18 print "指定保存/搜索目录" 19 def do_dir(self, pathname): 20 if pathname == "": pathname = raw_input("输入指定保存/搜索目录: ") 21 22 def help_find(self): 23 print "搜索关键词" 24 def do_find(self, keyword): 25 if keyword == "": keyword = raw_input("输入搜索关键字: ") 26 print "搜索关键词:'%s'" % keyword 27 if __name__ == '__main__': # this way the module can be 28 cdc = PyCDC() # imported by other programs as well 29 cdc.cmdloop() 这个 90% 照抄过来的脚本纯粹是用来尝试 cmd 模块功能的,没有任何实际作用,只是 想通过打印输出信息,来印证自个儿的想法…… 猜测和学习 如何针对已证实有用的代码进行改造和学习? 猜测是最自然的方法,不用想着要学习什么浑厚的背景知识,只要按照自个儿的理解, 给当前可用的代码一个说法,使之支持自个儿的自由发挥就对了。 运行一下,其结果如图 CDay−2-2 所示: PCS404 “代码重构浅说” 中说明重构(Refactoring) 是现代软件开发工程中常用 的技巧,可以保证系统在可 用状态下持续不断地提高代 码品质!在 PCS404 中,浅 显地说明了一下重构思想及 常见的重构技巧。 PCS404 CDays“光盘故事” | 25 图 CDay−2-2 pycdc-v0.4.py 运行结果 经过几次尝试可以确认以下几点。 1. if __name__ == '__main__':是进行自测运行用的,具体怎么回事儿先不管,好用 就成。 2. class PyCDC(cmd.Cmd):是进行类定义的,然后可以使用 cdc = PyCDC() 进行实体 化,并使用类的各种功能。 3. def __init__(self):是类实体化时自动运行的初始化函式。 4. cmd.Cmd.__init__(self)是初始化基类? 不明白,反正要使用 cmd 模块,就这么来 好了。 5. def help_*() 和 def do_*()应该成对出现,一个是打印功能的帮助信息,一个是 实际干事儿的。 6. 利用 cmd 创建的应用可以自动处理各种意外情况,汇报*** Unknown syntax: xxx。 非常整齐对称的代码呀,小白不知道怎么可以不破坏代码的这种整齐,又可以加入原先 的功能代码…… 利用自个儿的模块 那么,如何直接利用暂存到 cdctools.py 的功能呢? 1 # -*- coding: utf-8 -*- 2 import os 3 def cdWalker(cdrom,cdcfile): 警告:运行时总是汇报的一 个 Non-ASCII character '\xe6' ...警告,这是个中文问 题,只要不影响我们的尝试, 先不理会它,技术和知识是 无限的,我们要先关注自个 儿的目标,适当地放弃,有 利于提高开发效率。 26 | 实例故事 4 export = "" 5 for root, dirs, files in os.walk(cdrom): 6 export+="\n %s;%s;%s" % (root,dirs,files) 7 open(cdcfile, 'w').write(export) 8 if __name__ == '__main__': # this way the module can be 9 CDROM = '/media/cdrom0' 10 cdWalker(CDROM,"cdc/cdctools.cdc") 询问行者得到的提示是: from 某脚本文件名 import * 立即拿来改造 pycdc-v0.4.py: 1 # -*- coding: utf-8 -*- 2 import sys, cmd 3 from cdctools import * 4 ... 5 if __name__ == '__main__': # this way the module can be 6 cdc = PyCDC() # imported by other programs as well 7 cdc.cmdloop() 8 CDROM = '/media/cdrom0' 9 cdWalker(CDROM,"cdc/cdctools.cdc") 哈哈哈!可以运行! 而且悟出来一点,在代码中,按代码的复用尺度来分,从小到大应该 是: 代码行→函式→类→模块 好像还有更大的一级包,具体现在还用不上,就先不管它了。 “CDROM”这种定量字串怎么融合到类中呢? 还有,怎么令交互式命令行软件一运行就会 给点提示什么的? 小白的想象力随着不断地尝试成功,也不断地膨胀起来,那么就研究文档吧…… pycdc-v0.5.py 研究和改造的成果如下: 1 # -*- coding: utf-8 -*- 2 import sys, cmd 3 from cdctools import * 4 class PyCDC(cmd.Cmd): 5 def __init__(self): 6 cmd.Cmd.__init__(self) # initialize the base class PCS108“函式”、PCS105 “对象”、PCS110“逻辑分 支”进一步说明在不同级别 的 Python 执行单位中,有 哪些基础技巧。 PCS PCS100“import”进一步说 明了模块和包的使用方法和 注意事项。 PCS100 PCS201“cmd”详细描述了 cmd 模块功能,读者可以进 一步了解此模块的所有功 能。 这是一个辅助用户快速完成 一个交互式命令行环境的强 力模块,在线相关文档是: http://aspn.activestate.com /ASPN/docs/ActivePython/ 2.4/python/lib/module-cmd. html 精巧地址: http://bit.ly/bVos1 PCS201 CDays“光盘故事” | 27 7 self.CDROM = '/media/cdrom0' 8 self.CDDIR = 'cdc/' 9 self.prompt="(PyCDC)>" 10 self.intro = '''PyCDC0.5 使用说明: 11 dir 目录名 #指定保存和搜索目录,默认是 "cdc" 12 walk 文件名 #指定光盘信息文件名,使用 "*.cdc" 13 find 关键词 #使用在保存和搜索目录中遍历所有.cdc 文件,输出含有关键词的行 14 ? # 查询 15 EOF # 退出系统,也可以使用 Crtl+D(Unix)|Ctrl+Z(Dos/Windows) 16 ''' 17 def help_EOF(self): 18 print "退出程序 Quits the program" 19 def do_EOF(self, line): 20 sys.exit() 21 22 def help_walk(self): 23 print "扫描光盘内容 walk cd and export into *.cdc" 24 def do_walk(self, filename): 25 if filename == "":filename = raw_input("输入 cdc 文件名:: ") 26 print "扫描光盘内容保存到:'%s'" % filename 27 cdWalker(self.CDROM,self.CDDIR+filename) 28 29 def help_dir(self): 30 print "指定保存/搜索目录" 31 def do_dir(self, pathname): 32 if pathname == "": pathname = raw_input("输入指定保存/搜索目录: ") 33 self.CDDIR = pathname 34 print "指定保存/搜索目录:'%s' ;默认是:'%s'" % (pathname,self.CDDIR) 35 36 def help_find(self): 37 print "搜索关键词" 38 def do_find(self, keyword): 39 if keyword == "": keyword = raw_input("输入搜索关键字: ") 40 print "搜索关键词:'%s'" % keyword 41 42 if __name__ == '__main__': # this way the module can be 43 cdc = PyCDC() 44 cdc.cmdloop() 运行结果如图 CDay−2-3 所示。 28 | 实例故事 图 CDay−2-3 pycdc-v0.5.py运行结果 完全与预想吻合! 1. from cdctools import * 可以引入己有脚本中的所有函式。 2. 在恰当的位置追加 cdWalker(self.CDROM,self.CDDIR+filename) ,就完成了实际 功能的绑定。 3. self. 在类声明中应该是代表自个儿,代表实体化后的那个自个儿。 4. 类的定量应该都在__inti__(self) 初始化时声明。 5. cmd 的 prompt 就是命令行环境的前缀字串。 6. cmd 的 intro 果然是命令行环境的最初提示。 7. 括起来的多行字串,可以被自然地记录和打印到命令行环境中。 实现 grep 两大功能之一(扫描光盘信息记录为文件)通过:dir 和 walk 命令都实现了,现在就 差搜索了,其实就是像一个古老的软件 grep 一样,打开所有符合要求的文件,读取每一 行,如果有指定关键词在行内就打印输出到屏幕…… 结合已有的经验,可以非常简单地实现! 1 # -*- coding: utf-8 -*- 2 3 def cdcGrep(cdcpath,keyword): 4 filelist = os.listdir(cdcpath) # 搜索目录中的文件 5 for cdc in filelist: # 循环文件列表 6 if ".cdc" in cdc: # 过滤可能的其它文件,只关注.cdc 7 cdcfile = open(cdcpath+cdc) # 拼合文件路径,并打开文件 8 for line in cdcfile.readlines(): # 读取文件每一行,并循环 CDays“光盘故事” | 29 9 if keyword in line: # 判定是否有关键词在行中 10 print line # 打印输出 对于小白来说,在这个自制的最简单的 grep 功能中,6 行代码中的 5 行都可以自然地写 出,只有一行判定代码是新的: if keyword in line: in 也是计算,是 Python 中所有数据结构都支持的一种基础的方便的运算。可以在 Python 交互行命令行环境中快速测试一下: >>> a='123456' >>> '1' in a True >>> a=(1,2,3,4) >>> 1 in a True >>> a=[1,2,3,4] >>> 1 in a True >>> a={1:11,2:22,3:33} >>> 1 in a True 小结 通过对 cmd 模块的边学边用,小白今天至少可以体验到: 1. 类 2. 多行注释 3. 模块 4. 模块自测 5. 字串包含计算 虽然获得的可用代码的行数一下子冲破了个位数,但是 90% 的代码都可以在头两天的 4 行代码中找到模式。 练习 1. 在前文的 grep 实现例子中,没有考虑子目录的处理方式,因为如果直接 open 目录进行读 grep 是古老实用且高效的 模式文本匹配工具,在所有 的 Unix/Linux 系统中都会默 认安装,它最常做的事儿是 将一堆文本中包含某个模式 的文本行找出来,如: ~$ cat /proc/cpuinfo | grep core core id :0 cpu cores :2 core id :1 cpu cores :2 30 | 实例故事 操作,会出现错误,所以请读者修改这个示例代码,以便考虑到子目录这种特殊情 况,然后把最后探索出的 cdcGrep()嵌入 pycdc-v0.5.py 中,实现完成版本的 PyCDC。 提示:子目录处理,可以先判断,如果是子目录,就可以递归调用 cdcGrep()函式。 2. 编写一个类,实现简单的栈。数据的操作按照先进后出(FILO)的顺序。主要成员 函式为 put(item),实现数据 item 插入栈中;get(),实现从栈中取一个数据。 CDays“光盘故事” | 31 CDay−1 实用化中文 中文处理,完成功能的实用化 你碰到 99%的问题,其他人之前已经遇到过了,所以,最佳的解决方式就是找到那段别 人解决相似问题的代码! 回顾需求 小白已经实现的需求可以实现如下功能。 1. 可以扫描光盘内容,并存储为硬盘上的文本文件。 1)存储成*.cdc 的文本文件; 2)快速指定保存目录; 3)快速指定保存的文件名。 2. 可以根据储存到硬盘上的光盘信息进行搜索。 1)搜索指定目录中所有*.cdc 文件; 2)指定关键字进行搜索; 3)列出所有含有关键字的信息行。 进一步尝试 回想起来,一直尝试搜索的都是英文关键字,中文的没有试过。 尝试来几下!……呜乎,什么也查不出来! 32 | 实例故事 查阅记录文本 先来看看图 CDay−1-1。 图 CDay−1-1 mymusic-1.cdc 内容 这种数据对吗? 当初为了简单,使用文档中的基本型,即: #'cdctools.py' 中 cdWalker(cdrom,cdcfile) 的动作 ... for root, dirs, files in os.walk(cdrom): export+="\n %s;%s;%s" % (root,dirs,files) ... 就是使用 os.walk() 的天然输出组织成每一行: /media/cdrom0/EVA/Death-Rebirth;[];['eva8-01.Mp3', 'eva8-02.Mp3',...] ^ ^ ^ ^ | | | +- files 列表,此目录的文件名 | | +- 各个数据段使用";" 分隔 | +- dirs 列表,子目录名,如果没有就为空 +- 当前目录 瞧着格式顶像,为什么到中文的地方就成了问号呢? 中文!永远的痛 不问不知道,一把辛酸泪啊!在网络中一搜索才知道,只要是个中国人,不论整什么开 发,中文!永远会遇到各种问题的。不过,幸好比小白勤劳的人海了去,有关中文的 Python 处理建议一搜一大堆。但是,有时候,选择太多也是个问题。 CDays“光盘故事” | 33 编码问题 图 CDay−1-2 编码思维图谱 有行者给出如图 CDay−1-2 所示的思维图谱(Mind Map),目前还在理解过程中,先使用 已知的方式测试本地硬盘文件的目录情况,如图 CDay−1-3 所示。 图 CDay−1-3 本地文件目录的测试结果 看着就不同,还得根据理解继续尝试,从而知道自己是否真正正确理解,另外一些测试 结果如图 CDay−1-4 所示。 图 CDay−1-4 另一些测试结果(使用 unicode) 34 | 实例故事 unicode(原始文本, 'utf8' ).encode('utf8') 文本 ==decode()--> [unicode] ==>encode()--> utf-8 文本 ^ ^ ^ ^ ^ | | | | +- 最终的渴求 | | | +- 此为编码过程;可以从 unicode 输出为任意编码 | | +- Python 内置支持的 unicode 格式数据 | +- 此为解码过程,将已知编码的文本编译成宇宙通用的 unicode 数据 +- 原始文本信息,是什么编码你得知道! 也就是说文件没有编码之说,其实都是按二进制格式保存在硬盘中的,仅仅是在写入读 取时须使用对应的编码进行处理,以便操作系统配合相关软件/字体,绘制到屏幕中给人 看。所以关键问题是得知道原先这些字串数据是使用什么编码来编译的!但是在 Unicode 之前都是使用类似对照表的形式来组织编码的,无法从串数据流本身统一解出不同的文 字来。 只有猜! 猜编码函式一 def _smartcode(stream): try: ustring = unicode(stream, 'gbk') except UnicodeDecodeError: try: ustring = unicode(stream, 'big5') except UnicodeDecodeError: try: ustring = unicode(stream, 'shift_jis') except UnicodeDecodeError: try: ustring = unicode(stream, 'ascii') except: return u"bad unicode encode!" 收集的音乐可不仅仅中文的,印度、伊朗等国的音乐都有可能收集,如果逐一去尝试, 这个判定树就太可怕了! 升级!使用 Python 的数据对象进行集成! 猜编码函式二 def _smartcode(stream): tryuni = ("gbk" ,"gb2312" CDays“光盘故事” | 35 ,"gb18030" ,"big5" ,"shift_jis" ,"iso2022_kr" ,"iso2022_jp" ,"ascii") try: for type in tryuni: try: ustring = unicode("%s}"%type+stream, type) #try decode by list except: continue #break!continue try decode guess except: return u"bad unicode encode!" 但是…… chardet 这么一项项猜,还是显得很傻,万一有些字的高位在不同编码中是相同的,那真的是只 能撞大运了!尤其是对可怜的难兄难弟:gbk 和 big5。 比如: >>> print '变巨'.decode('big5') 曹操 >>> print '变巨'.decode('gbk') 变巨 再问行者,他们给出个地址:http://chardet.feedparser.org/ ,上面有 Character encoding auto-detection (自动字符探测器)。 真的有呀! 而且是 Mozilla 使用的! 立用不疑! 怎么安装外部模块呢?软件包下载,解开压缩,嗯?没有 INSTALL 说明文件,但是有个 setup.py ,尝试执行一下(如图 CDay−1-5 所示)。 PCS6“Python 与中文”进 一步全面地阐述了在 Python 中面对中文数据时 的思路和技巧。 PCS6 PCS112“异常”讲述了 Python 中异常的使用,即 try ... except ...的使用等。 PCS112 36 | 实例故事 图 CDay−1-5 chardet 安装示意 -$1s-l 总用量为 33。 drwxr-xr-x 3 zoomq zoomq 72 2008-04-29 11:25 build drwx------ 2 zoomq zoomq 1264 2006-01-11 01:34 chardet -rwx------ 1 zoomq zoomq 26432 2006-01-11 01:34 COPYING drwxrwxrwx 4 zoomq zoomq 296 2006-01-11 01:34 docs -rwx------ 1 zoomq zoomq 1981 2006-01-11 01:34 setup.py ~$ python setup.py usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] or: setup.py --help [cmd1 cmd2 ...] or: setup.py --help-commands or: setup.py cmd --help error: no commands supplied ~$ sudo python setup.py install running install running build running build_py running install_lib running install_egg_info Removing /usr/lib/python2.5/site-packages/chardet-1.0.egg-info Writing /usr/lib/python2.5/site-packages/chardet-1.0.egg-info so easy! 好像使用了类似小白已经完成的交互式提醒呀! CDays“光盘故事” | 37 看来所有 Python 的软件都可以通过 python setup.py install 进行安装! 1 import chardet 2 def _smartcode(stream): 3 """smart recove stream into UTF-8 4 """ 5 ustring = stream 6 codedetect = chardet.detect(ustring)["encoding"] 7 print codedetect 8 try: 9 print ustring 10 ustring = unicode(ustring, codedetect) 11 print ustring 12 return "%s %s"%("",ustring.encode('utf8')) 13 except: 14 return u"bad unicode encode try!" 经过测试,已确信它在各种情况下都可以正确识别!但是不论怎么尝试,已经保存下来 的.cdc 文本依然是 ASCII 码! 另外的思路 也许真的就是 ASCII 码呢? 幸福的自由 冷静一下,既然,文件没有编码,都是二进制的,那么光盘文件是如何被系统认识的?! 在列表中吼了一下,行者有点无奈地说:“TiosnG!”——There is one site named Google! 好吧,小白老实地搜索了一番,发现了 iso9660 ——所有光盘基本都是此文件格式的,同 M$的 FAT32/ntfsGNU/Liunx 使用的 ext2/3、Unix 使用的 UFS...一样只是种文件系统。那 么它必然是通过某种软件进行转换从而可以访问的。在 Ubuntu 中当然是标准地调用朴 实强大的 mount 命令了。 ~$ cat /etc/mtab ... /dev/sda3 /dos vfat rw,utf8,umask=007,gid=46 0 0 ... /dev/sda1 /windows ntfs rw,nls=utf8,umask=007,gid=46 0 0 /dev/scd0 /media/cdrom0 iso9660 ro,noexec,nosuid,nodev,user=zoomq 0 0 ... PCS202“chardet”进一步 说明了此外部模块的信息, 并分享了使用体验。 PCS202 38 | 实例故事 观察默认的挂接光盘的行为和挂接 Dos 和 Windows 分区的情况,感觉是默认的挂接行为 没有使用编码处理。那么通过改变挂接的行为,应该可以改善读取不到正确文本的情 况。 #挂接成 GBK ~$ sudo mount -o ro,norock,iocharset=cp936 /dev/scd0 /media/cdrom0 #挂接成 UTF8 ~$ sudo mount -o ro,norock,iocharset=utf8 /dev/scd0 /media/cdrom0 果然!果然!相对 M$,系统安装成什么语言的系统,就永远使用该系统强行挂接光盘, 好像八成应该不可以轻易按照你的意愿改变的……以前小白对于自由软件的认识仅仅是 免费,难用,这下忽然间有所顿悟,不觉写下感慨: 自由软件好,我用我自在! 所谓自由,是基于对自个儿的了解,真正理解了自个儿想要什么之后,自由软件支持你 的一切尝试!哈哈哈! 小白可以感受到这些,应该算是小灰了,可以稳定地向成熟的小黑——一名黑客挺进了! 提示:Hacker(黑客)绝对不是中国媒体中宣传的那些攻击他人电脑的家伙,黑客是些 创造技术奇迹的单纯的人们(http://wiki.woodpecker.org.cn/moin/HackerHowto,精巧地址: http://bit.ly/31rUuY )。被翻译所误指的那类家伙是 Cracker (骇客) (http://en.wikipedia.org/wiki/Cracker,精巧地址:http://bit.ly/3Xx0A),他是破坏者,未经 授权而企图进入电脑系统者。这种入侵者通常会恶意进入他人的系统,而且有许多技巧 可以破坏他人的系统。这个名词是骇客(Cracker)在 1985 年为对抗新闻媒体滥用 hacker 而提出的。1981~1982 年前,曾有人推动使用“毛虫”代表 Cracker,但并未成功。 改善数据结构 查询是可以了,但是,使用默认的列表打印格式来存储和汇报实在不咋的,想修改修改, 于是 #cdctools.py... def formatCDinfo(root,dirs,files): export = "\n"+root+"\n" for d in dirs: export+= "-d "+root+_smartcode(d)+"\n" for f in files: export+= "-f %s %s \n" % (root,_smartcode(f)) export+= "="*70 CDays“光盘故事” | 39 return export 存储下来的 .cdc 片段为: ... -f /media/cdrom0/RyokoHirosue/Ryoko Hirosue-files.files title.gif ====================================================================== /media/cdrom0/RyokoHirosue/成长物语 -d /media/cdrom0/RyokoHirosue/成长物语 音乐极限--成长物语.files -f /media/cdrom0/RyokoHirosue/成长物语 RH991101.mp3 -f /media/cdrom0/RyokoHirosue/成长物语 RH991102.mp3 d 代表目录;f 代表文件。它们分别是 directory(目录)和 file(文件)的缩写。 小结 这日,小白仅仅解决了一个问题——中文搜索问题;解决的尝试路径及相互关系如图 CDay−1-6 所示。但是,实际上他获得了两大方面的进步: 1. 有了文本编码方面的知识。 2. 问题解决的方法论境界提高了一个层次,开始使用系统级别的解决方案了。 相对 Python 方面,仅仅追加了一对内置函式和一个外部模块包使用的体验。 关键词: 编码 unicode chardet mount 注意:如果你使用 M$ 系统,运气好的话是不会见到笔者列出的现象的,但是不保证以 后遇不到自动处理下搞不定的光盘内容。希望那时候小白真的有个自由环境可以尝试其 他方案。 提示:事实上存在 Windows 下面的完全 Unix 环境,如 Cygwin(http://www.cygwin.com), 它是一个通过运行于 Windows 下的免费的 Unix 子系统使用一个 Dll(动态链接库)来实 现的虚拟机,可以直接在 Windows 环境中使用各种 Unix 实用工具。 PCS107 “字串格式化”进 一步介绍了用来快速重构光 盘信息文本格式的 Python 所内置的字符模板支持特 性。 PCS107 40 | 实例故事 图 CDay−1-6 CDay−1 问题解决思路图谱 练习 1. 自动判定你自个儿或是朋友的 Blog 是什么编码的? 2. 如果是非 utf-8 的,请编写小程序自动将指定文章转换成 utf-8 编码保存。 CDays“光盘故事” | 41 CDay0 时刻准备着!发布 感受软件工程进行发布的准备 发布!为了全人类!因为一个人如果力求完善自己,他就会明白同时也必须完善他人。 一个人如果不关心别人的完善,自己便不可能完善。 从需求导出用户群 小白已经实现的功能如下所示。 1. 扫描光盘内容并存储为硬盘上的文本文件。 1) 存储成*.cdc 的文本文件; 2) 可以快速指定保存目录; 3) 可以快速指定保存的文件名。 2. 根据储存到硬盘上的光盘信息计件进行搜索。 1) 可以搜索指定目录中所有*.cdc 文件; 2) 可以指定关键字进行搜索。 列出所有含有关键字的信息行。 对于大胆并逐渐习惯命令行界面的小白来说,现在的 PyCDC 基本可用了。那么这样的 小工具,有哪些人会愿意使用? 推想一下: 有很多光盘的人 知道并会安装 Python 的人 不惧怕命令行的人 42 | 实例故事 勇于尝试的人 这样的人也必定是愿意分享的人,小白受行者的熏陶,也想把刚刚完成的可用的 PyCDC 汇报给列表中的前辈们使用,一是想获得夸奖,不过更加希望有人通过使用反馈意见, 甚至于代码的改进意见,发布出来,进而可以将自己首个作品长期发展下去,造福人类。 所以要发布! 发布的本质 发布好像不是通告一嗓子就可以了的事儿。 给人用 小白尝试将 PyCDC 整理了个压缩包发送给朋友,结果所有的朋友都说不会使用……郁 闷是小白的真实写照。 只好再次到列表中询问:“怎么发布软件啊?!”行者回复:“文档!文档!文档!” 文档 分,分,分!学生的命根! 文档,文档,文档!软件的颜面! 软件是给人使用的,但是你无法跑到每个用户身边,去演示如何使用你的软件吧?!所以, 文档!友好的文档!清晰的文档!有效的文档! 就是作者的代言人、形象大使,是潜在 参与者/体验者的向导,是帮助他人学习/使用/改进你的软件的实用拐杖!对于使用相关许 可证进行源代码发布的自由软件来说,文档也是工程的一部分!面对乱七八糟的文档, 是没有人敢于使用你的软件的。 再次开发 所以,发布意味着针对你期望的用户再次开发! 标准的自由软件包应该有哪些文档? 1. AUTHORS 作者自述 2. LICENSE 许可证类型(Python 世界喜欢简单的 BSD 系列许可证) CDays“光盘故事” | 43 3. README 软件说明 4. ChangeLog 修订历史 5. PKG-INFO 包信息,提供给一些自动程序使用 这些都是必需的类似八股文的内容。更加重要的是,小白对自个儿经过反复尝试总结出 来的多个小巧核心功能,非常期望他人可以重复使用以下函式! cdWalker() cdcGrep() _smartcode() 那么如何更友好地说明如何单独使用这些函式呢? 进而,随着代码的频繁变化,怎样才 能令这些函式便于维护? 问行者,才知道原来这叫文档化开发——将软件 API 文档同代码结合起来写,写代码的 同时也完成了文档,它是种常用的开发、维护同时进行的开发模式。 文档化开发的主要技巧,就是利用注释! 但是使用什么格式的注释?注释是通过什么工具转换成文档的? 行者又推荐了 epydoc。 epydoc epydoc ,这是一个轻松的 Py 文档生成器(Easy Py Documentor )!其网址为: http://wiki.woodpecker.org.cn/moin/CodeCommentingRule(精巧地址:http://bit.ly/46BkrB) 在网址 http://epydoc.sourceforge.net/api/epydoc-module.html(精巧地址:http://bit.ly/250krV) 上可以看到,epydoc 自个儿的文档输出,是多么专业和规范! 在简要学习相关知识后,小白发觉只要起草一个简单的配置文件就可以了: ##可以命名为 epydoc.cfg [epydoc] # 项目信息 name: PyCDC url: http://wiki.woodpecker.org.cn/moin/ObpLovelyPython # 想进行处理的文件或是模块,一般指定目录即可 modules: /pat/to/pycdc.py,/pat/to/cdctools.py # 使用 html 格式,输出文档到 apidoc/ 目录中 output: html PCS104“注释”进一步介绍 了 Python 中注释的写法, 配合注释可以自动进行开发 文档生成的工具⋯⋯ PCS104 44 | 实例故事 target: apidocs/ # 指定 Graphviz dot.所在的位置,可以生成各种 UML 关系图谱(又一项自由环境中的福利)! graph: all dotpath: /usr/bin/dot 这样一来,小白就可以随时使用类似于“~$ epydoc --config epydoc.cfg”的命令获得 最新的 API 文档,效果如图 CDay0-1 所示: 图 CDay0-1 epydoc 生成的 API 文档 它是非常类似 Java DOC 带框架的综合视图的文档站点! 引发的改进 函式命名的合理性 小白自个儿之前随手命名的函式,在注释中,前后看起来并不顺畅,回想一路使用着的 Python 内置函式命名: 1. os.listdir() 2. os.walk() 3. encode() 4. open().write() 它们是多么地直接啊,小白现在才发现,命名也是大学问,于是查看了 Python 开发编码 规范(http://wiki.woodpecker.org.cn/moin/PythonCodingRule)。 简单地说就是要应用前人在大量的代码编写中摸索出来的规范,这些规范可以令任何人 PCS203 “epydoc”进一步 介绍了这一优秀的文档化开 发支持工具;所谓文档化开 发,就是将软件工程过程中 的相关文档,嵌入到相应代 码的注释中,通过简要的字 串约定,然后由工具自动摄 取出相应的文本,组织成可 以随时查阅的文档。 PCS203 CDays“光盘故事” | 45 快速在代码中区分出各种关键变量/类/函式等。 有关命名原则,小白现在可以理解并习惯了。 1. 各种命令尽量使用动宾式短语。 2. 目录/文件名全部小写。 3. 类名使用首字母大写单词串(WikiNames)。 4. 全局变量使用全大写字串(CAPWORD)。 5. 函式使用混合大小写串 (mixedCase)。 6. 内部变量、常数,全部小写。 综合应用就形如以下代码。 1 from mypacket import MyClass 2 GLOBALVAR="是也乎" 3 def doInsert(arg): 4 MyClass.myFunc(GLOBALVAR) 所以,小小修订一些变量/函式的命名,使之更加贴切是作者在发布前的礼貌。 cdctools._smartcode(stream)-> cdctools._smartcode(ustring) codedetect = chardet.detect(ustring)["encoding"]->codename 小结 通过本日故事小白正式登堂入室到软件工程世界,沟通需要代价,改进由我做起! 既然软件的发布就是和潜在的参与者沟通,沟通就需要良好的媒介,在软件世界, 软件工程文档是最好的中介; 自由软件世界,聪明人太多,连文档也有专门的辅助工具,小白快乐地使用并体验着; 对 epydoc 的体验将引导小白思考很多实现功能以外的事儿,这让小白越来越强了, 行动更加有章法,他正向靠谱的程序员健康挺进…… 练习 请根据软件发布的流程和软件开发的编码规范,将读者在前面章节所写的程序修改并发 布出去。另外,可以查找下除了 epydoc 外还有哪些较好的 py 文档生成器? PCS7“Python 编码规范” 完整地翻译了 PEP-0008 Guido 原创的"Python 风格 指南" http://www.python.org/dev /peps/pep-0008/ ( 精 巧 地 址:http://bit.ly/2OQ8Z3) 这是写出靠谱的 Python 脚 本的基础素养,至少应该认 真反复精读 3 回。 PCS7 46 | 实例故事 CDay+1 优化!对自个儿的反省 首次重构优化数据结构 没有最好,只有更合理! 扩展需求 到目前为止,小白已经实现的功能为: 1. 扫描光盘内容并存储为硬盘上的文本文件。 1) 存储成*.cdc 的文本文件 cdctools.cdWalker(); 2) 可以快速指定保存目录 PyCDC.do_dir(); 3) 可以快速指定保存的文件名 PyCDC.do_walk()。 2. 根据储存到硬盘上的光盘信息文件进行搜索。 1) 可以搜索指定目录中所有*.cdc 文件 cdctools.cdcGrep(); 2) 可以指定关键字进行搜索 PyCDC.do_find()。 列出所有含有关键字的信息行。 而且,小白已经组织好了文档化的注释,足以让有心人快速了解程序整体的结构,复用 相关函式,或是掺和持续改进。这不,还真有另外的小白提出了问题。 问一声 “为什么叫 PyCDC ? 和 CD Catalog Expert 有关系吗?”有好事者先置疑了名称。 小白心想:先随便起的名字,含义应该是 “CD Collector”或是“CD Commander”,难道 已经有同类甚至同名的软件了?搜索一下,找到这个网址:http://www.zero2000. CDays“光盘故事” | 47 com/cd-catalog-expert/index.html(精巧地址:http://bit.ly/17N5fU)。 咦,还真有 CD Collection ,网址为 http://www.nicomsoft.com/cdc/ (精巧地址: http://bit.ly/4vsuwe),甚至于,类似通过抓取指定介质的信息进行外部管理的软件是一个大 类,叫 Disk Catalog。 Disk Catalog 有关磁盘的分类,找到的评比文章为:http://tech.sina.com.cn/c/2003-12-05/25149.html (精 巧地址:http://bit.ly/3U8vbr)。 而小白拍脑袋想出来的文本格式的光盘信息摘要形式,事实上是历史悠久的。 完全类似 Total Commander 的 DiskDir、CatalogMaker 等插件生成的 Catalog 摘要。 而在 Linux 下面,FTP 服务器自古就提供通过 ls -lR 生成的文本摘要。 对比及推导 那么哪种比较好? 小白当然愿意令自个儿的小工具发扬光大下去,那么改进是不可避免的!其实记录的摘 要文本已经升级过一次了,从 os.walk() 输出的自然结构格式为: /media/cdrom0/EVA/Death-Rebirth;[];['eva8-01.Mp3', 'eva8-02.Mp3',...] ^ ^ ^ ^ | | | +- files 列表,此目录的文件名 | | +- 各个数据段使用";" 分隔 | +- dirs 列表,子目录名,如果没有就为空 +- 当前目录 使用专门的 formatCDinfo() 函式格式化,为: ... -f /media/cdrom0/RyokoHirosue/Ryoko Hirosue-files.files title.gif ====================================================================== /media/cdrom0/RyokoHirosue/成长物语 -d /media/cdrom0/RyokoHirosue/成长物语 音乐极限--成长物语.files -f /media/cdrom0/RyokoHirosue/成长物语 RH991101.mp3 -f /media/cdrom0/RyokoHirosue/成长物语 RH991102.mp3 其中 d 代表目录;f 代表文件。 48 | 实例故事 虽然是自个儿随意设计的格式,但是小白可以体会出以下好处: 1. 格式自然,可以直接人工查阅; 2. 每行包含绝对必要的信息:对象类型(目录?文件?)、 所在目录、文件名; 3. 搜索匹配后的输出行就是自然行,并包含必要信息。 那么也快速考查一下其他数据格式: Linux 目录摘要命令:ls -lR ~/LovelyPython/CDays/cday0$ ls -lR .: drwxr-xr-x 3 zoomq zoomq 4096 2007-08-18 22:43 apidocs -rwxr-xr-x 1 zoomq zoomq 87 2007-07-31 14:58 AUTHORS drwxr-xr-x 3 zoomq zoomq 4096 2007-07-31 14:58 cdc -rwxr-xr-x 1 zoomq zoomq 3874 2007-08-18 22:42 cdctools.py -rw-r--r-- 1 zoomq zoomq 3758 2007-08-18 22:42 cdctools.pyc -rwxr-xr-x 1 zoomq zoomq 1152 2007-08-18 22:02 ChangeLog -rw-r--r-- 1 zoomq zoomq 694 2007-08-18 22:30 epydoc.cfg -rwxr-xr-x 1 zoomq zoomq 35148 2007-08-18 22:00 LICENSE -rwxr-xr-x 1 zoomq zoomq 246 2007-08-18 22:01 PKG-INFO -rwxr-xr-x 1 zoomq zoomq 3273 2007-08-18 22:28 pycdc.py -rw-r--r-- 1 zoomq zoomq 4634 2007-08-18 22:28 pycdc.pyc -rwxr-xr-x 1 zoomq zoomq 1082 2007-07-31 14:58 README ./apidocs: -rw-r--r-- 1 zoomq zoomq 760 2007-08-18 22:52 api-objects.txt -rw-r--r-- 1 zoomq zoomq 15247 2007-08-18 22:52 cdctools-module.html -rw-r--r-- 1 zoomq zoomq 26380 2007-08-18 22:52 cdctools-pysrc.html -rw-r--r-- 1 zoomq zoomq 4139 2007-08-18 22:52 class-tree.html -rw-r--r-- 1 zoomq zoomq 340 2007-08-18 22:52 crarr.png ... 这个命令是以空行来区分不同目录下的信息的。在这个命令中,每节是固定顺序的信息: ./apidocs: <-- 当前目录 -rw-r--r-- 1 zoomq zoomq 760 2007-08-18 22:52 api-objects.txt ^ ^ ^ ^ ^ ^ ^ | | | | | | +- 文件/目录名 | | | | | +- 创建/修订时间 | | | | +- 体积 | | | +- 用户组 | | +- 用户 | +- 权限组 +- 文件(-)或是目录(d) CDays“光盘故事” | 49 它包含了丰富且规范的信息,但是作为 PyCDC,其中有关时间/权限的信息都是不必要 的,而且不是跨平台兼容的,在 M$平台中,权限信息就根本用不上。 CD Catalog Expert 的文本数据格式如下所示: [Info] DriveType=5 ImagePath=L: Volume=MCollec.39 Serial=723840260 FAT=CDFS DiskSize=676020224 DiskFree=0 [Comment] 0= South Park(南方公园) 1= 可儿乐队精选 The Corrs Greathits 2= 林忆莲 3= 瘐澄庆《哈林天堂》Harlem'm 4=Heaven [L:] [L:\South Park(南方公园)] South Park - Merry Fucking Xmas.mp3=1986560 South Park - Uncle Fucker.mp3=1056768 ... 这样一来,小白也看明白了,完全是标准的.ini 配置文本格式! [Info] 一节是软件和光盘的整体信息;[Comment] 一节是根目录列表;[目录] 各节是各 个目录的文件信息。 和自创的文本格式相比这个格式更加规范,而且可以直接由成熟的己有软件解读!小白 突然开窍……这等于是直接利用既有客户群! 有兼容性的话,原先使用 CD Catalog Expert 的人不也更加有可能使用 PyCDC 了?至于其他使用数据库或是自创二进制文件 的软件,小白没有精力和兴趣研究了,就这么着! 快速重构 让 PyCDC 的光盘摘要文本格式,直接向己有软件的数据格式看齐! 50 | 实例故事 问题探索 有了目标就有了研究的动力!小白立即根据分析结果确定了代码难点: 1. 怎么处理 .ini 文件? 2. 怎么获得文件的大小? 老规矩,在列表中吼! 结果,行者们给出来的答案是一致的:内置模块有支持! 呜乎……看来犯了依赖病,小白重新拿起《提问的智慧》BS 一下自个儿,然后,翻开 Global Module Index(全局模块索引)这一大通乱找,果然看到了! ConfigParser ConfigParser(配置处理机)?! 这也忒直白了吧! 粗略地看了一下,也就 RawConfigParser-objects( 原始配置处理机对象)有写出到文件 的函式,就它了!在 iPython 中快速尝试使用一下(如图 CDay1-1 所示)。 图 CDay1-1 ConfigParser使用示意 果然好使! 注意:图 CDay1-1 截取的是真实的尝试过程,不过由于笔者使用的是增强的 Python 交互 环境“iPython”,所以,在图的[14]处,可能会让读者有所误解,这里是使用 tab (制表 符键)时,iPython 自动给出的猜测输出,以便用户选定/回忆出真正想要的对象/文件等, 这里并不像其他交互环境中使用 Enter (回车键)! PCS204“ConfigParser”进 一步详细地分享了使用这一 常用内建模块的技巧。 PCS204 PCS2“交互环境之 iPython” 分享了这一优秀的可以提高 工作效率的环境体验! PCS2 CDays“光盘故事” | 51 文件?是 os 的 文件大小信息在哪?以 f 开头的模块都不像呀!小白突然想起来,这文件和目录都是文件 系统,应该是操作系统管理的。比如光盘扫描的 walk()函式就是在 os 模块中的,文件这 个信息也应该在那里? Files and Directories(文件及目录)果然! 有个 os.stat() 系统状态函式! 照例在 iPython 中快速尝试使用一下(如图 CDay1-2 所示)。 图 CDay1-2 os.stat()使用示意 Bingo! 就它了! 完成重构 简单地说就是利用基础配置处理机模块,将原先 os.walk() 生成的信息组织成类 ini 的 文本保存下来: 1 def cdWalker(cdrom,cdcfile): 2 '''光盘扫描主函式 3 @param cdrom: 光盘访问路径 4 @param cdcfile: 输出的光盘信息记录文件(包含路径,绝对、相对都可以) 5 @return: 无,直接输出成*.cdc 文件 6 @attention: 从 v0.7 开始不使用此扫描函式,使用 iniCDinfo() 7 ''' 8 export = "" PCS200 “ os(.stat;.path) ” 中名为 os 的内建模块里不 仅仅有 stat(),还有其他大 量的有关操作系统的好用工 具! PCS200 52 | 实例故事 9 for root, dirs, files in os.walk(cdrom): 10 print formatCDinfo(root,dirs,files) 11 export+= formatCDinfo(root,dirs,files) 12 open(cdcfile, 'w').write(export) 13 14 def formatCDinfo(root,dirs,files): 15 '''光盘信息记录格式化函式 16 @note: 直接利用 os.walk() 函式的输出信息进行重组 17 @param root: 当前根 18 @param dirs: 当前根中的所有目录 19 @param files: 当前根中的所有文件 20 @return: 字串,组织好的当前目录信息 21 ''' 22 export = "\n"+root+"\n" 23 for d in dirs: 24 export+= "-d "+root+_smartcode(d)+"\n" 25 for f in files: 26 export+= "-f %s %s \n" % (root,_smartcode(f)) 27 export+= "="*70 28 return export 1 def iniCDinfo(cdrom,cdcfile): 2 '''光盘信息.ini 格式化函式 3 @note: 直接利用 os.walk() 函式的输出信息由 ConfigParser.RawConfigParser 进行重组处理成 .ini 格式文本输出并记录 4 @param cdrom: 光盘访问路径 5 @param cdcfile: 输出的光盘信息记录文件(包含路径,绝对、相对都可以) 6 @return: 无,直接输出成组织好的类.ini 的*.cdc 文件 7 ''' 8 walker = {} 9 for root, dirs, files in os.walk(cdrom): 10 walker[root]=(dirs,files) # 这里是个需要理解的地方 11 cfg = rcp() 12 cfg.add_section("Info") 13 cfg.add_section("Comment") 14 cfg.set("Info", 'ImagePath', cdrom) 15 cfg.set("Info", 'Volume', cdcfile) 16 dirs = walker.keys() 17 i = 0 18 for d in dirs: 19 i+=1 20 cfg.set("Comment", str(i),d) CDays“光盘故事” | 53 21 for p in walker: 22 cfg.add_section(p) 23 for f in walker[p][1]: 24 cfg.set(p, f, os.stat("%s/%s"%(p,f)).st_size) 25 cfg.write(open(cdcfile,"w")) 有几个地方要注意一下。改进后的函式第 10 行,应该理解为: 使用字典保存目录结构信息; 以根目录作为 key(键),对应子目录加文件作为 value(值); 为了便于后续的处理,可以根据路径信息,自然地获得进一步的文件信息!并输出 到文件的对应数据节中。 done! 当然还有其他连带的改动: 1. 命令行相关响应的实际处理更新。 2. 搜索的相应升级。 这些都是小改动了,随手就得。 小结 在本日故事中,小白通过自发的反省,对比了数据结构的优劣,选择了自个儿最容易理 解,且跨平台通用的数据格式,照例在行者提醒后发觉并使用内建模块 ConfigParser 来 进行数据文本的组织和解读。至少可以体验到: 1. 文本也是数据,结构化的文本不仅仅可以用来作配置文件,也完全可以作为专门的 数据库。 2. os 模块包含了在操作系统中文件的所有主要操作。 练习 1. 编程实现以下功能,并进行最大化的优化:遍历指定目录下的所有文件,找出其中 占用空间最大的前 3 个文件。 2. 利用 ConfigParser,将上述题目中产生的结果按照 cdays+1-my.ini 格式存储到文件 cdays+1-result.txt 中。 PCS208 “dict4ini”实际上 是针对 .ini 格式文本数据 的,行者曾经自定义了相关 模块,进行更加 OOP 的操 作!dict4ini 就是其中一种第 三方模块,在 PCS208 中有 相关描述,有兴趣的可以深 入阅读。 PCS208 54 | 实例故事 CDay+2 界面!不应该是难事儿 用户界面友好化 网站软件化绝对不是空话! 清点需求 小白开始自豪地清点到目前为止已经自主实现的功能。 1. 扫描光盘内容并存储为硬盘上的文本文件。 1) 存储成*.cdc 的文本文件,采用函式 cdctools.cdWalker(); 2) 可以快速指定保存目录,采用函式 PyCDC.do_dir(); 3) 可以快速指定保存的文件名,采用函式 PyCDC.do_walk()。 2. 根据储存到硬盘上的光盘信息文件进行搜索。 1) 可以搜索指定目录中所有*.cdc 文件,采用函式 cdctools.cdcGrep(); 2) 可以指定关键字进行搜索,采用函式 PyCDC.do_find()。 列出所有含有关键字的信息行。 使用 GUI 有必要吗?没必要吗 “能不能给个界面啊?每次都要输入命令,很麻烦的……” 呜乎,果然有这种主流反馈汹涌而来了! 小白一边腹诽,一边也在憧憬:“不习惯使用命令行的都是初级用户!咳,俺也喜欢图形 界面的……但是好难的!”不用去列表询问,小白也知道 Python 无所不能,一搜索就知 CDays“光盘故事” | 55 道至少有: 1. Tk 2. wxPython 3. Qt 三大 GUI 框架可以快速组织桌面软件界面出来,但是,就算是写出来了,也无法简单地 发布成友好的坚固的稳定的傻瓜化的安装程序呀! 在列表中一问才知道,虽然有 N 多发布打包框架,但是都至少得包含完整的 Python 虚拟 机环境,仅这一项就是 5MB,想来核心代码不超过 200 行的东西,发布成过 MB 的东西, 小白就感觉脸红……这不白玩 Python 了?一点也不 Cool! 咋办咧?倒个苦水先,在社区列表中行者轻轻一句话惊醒梦里人! “浏览器也是 GUI!” “浏览器也是 GUI” 现在不都流行网站软件化吗? HTML+浏览器,可不就是用户最熟悉,而且最有宽容心 理的界面吗?! 注意:SaaS(Software-as-a-Service,软件即服务),官方网站:http://www.saas.com.cn/, 指出了当前网站的运营服务化、功能软件化的趋势。 好啦!就整个本地站点式的界面出来,要求用户先安装好 Python 环境,然后,解开压缩 包,不用安装,运行一条命令,就可以通过浏览器看到一个本地功能网站来使用 PyCDC! Web 应用框架 有时候选择太多,也是种痛苦……再次感叹一下!小白刚刚一动念头,就搜索到这种页面: WebFrameworks:http://wiki.python.org/moin/WebFrameworks 精巧地址:http://bit.ly/3jxUSJ Python WEB 应用框架纵览:http://wiki.woodpecker.org.cn/moin/PyWebFrameList 精巧地址:http://bit.ly/1U4oHm 密密麻麻一大片应用框架…… 56 | 实例故事 “俺不想弄出个复杂的强大的有 DB 支持等高级特性的网站,俺就是想用 Python 快速完 成一个有限功能的本地网站呀!”小白在列表中吼道。 很快,行者给出了回应:“Karrigell”。 Karrigell 最简单和省心的 Web 应用框架,就属 Karrigell 了,实在是最听话的一种。 好吧,那就尝试使用 Karrigell 来整个桌面网站型的界面! 介绍 http://karrigell.sourceforge.net/ 主站的介绍也非常精妙。Karrigell 为了大家不同的习惯居 然提供了多达 5 种 Web 应用发布形式: 1. 自然 Python 脚本 1 #hallo.py 2 print "

Squares

" 3 for i in range(10): 4 print "%s :%s" %(i,i*i) ——有大量的 HTML 要亲自组织和输出?不直爽! 2. HTML 嵌 Python 脚本 #hallo.hip "

Squares

" for i in range(10): "%s :%s" %(i,i*i) ——同样得亲自组织 HTML 输出?不直爽! 3. Python 嵌 HTML 脚本 #hallo.pih

Squares

<% for i in range(10): print "%s :%s" %(i,i*i) %> ——这不就是 PHP 吗?没劲! PCS301“Karrigell”精要地 介绍了这一 Web 应用框架 的主要特性。 PCS301 PCS300“CherryPy”另外 介绍了这一历史悠久、思想 独到的 Web 应用框架。 PCS300 CDays“光盘故事” | 57 4. CGI 脚本 #hallo.py print 'Content-type: text/html' print print "

Squares

" for i in range(10): print "%s :%s" %(i,i*i) ——比自然 Python 还要麻烦,而且对发布目录有约束?不直爽! 5. Karrigell 服务 1 #hallo.ks 2 def index(): 3 print "

Squares

" 4 for i in range(10): 5 print "%s :%s" %(i,i*i) ——看起来好像支持函式化的组合? 就这种了! 文档 http://karrigell.sourceforge.net/en/front.htm(精巧地址:http://bit.ly/2z3ejE)有进一步 的细节介绍,不过小白已经有足够的信心立即使用 Karrigell 来启动自个儿的程序了。 运行 首先得将环境运行起来,如果可以边修改,边在页面中看到结果的话,就非常直爽了! 也不用问,看着简单的文档,一般就 3 板斧: 1. 下载,安装。 耶!Karrigell 居然是零安装,超级绿色的!解开压缩包,在任何地方都可以运行! 使用不包含 demo 文档的核心版本 Karrigell-core-2.3.5.tgz,解压缩了才 800 多 KB! 2. 配置。 在 Karrigell-2.3.5 目录中找到 Karrigell.ini(注意:在 Karrigell 2.4 版本之后, 配置文件移动到 conf 目录中了),然后找到: ... [Server] # set the port on which Karrigell will serve requests # default is 80 # on Unix/Linux, if you are not logged as root you may not be able 58 | 实例故事 # to start on a port below 1024 for security reasons #port=8081 仅仅修订一行: port=8081 去掉注释,声明将网站发布到 :8081 端口,避开用户可能已有的默认:80 网站。 3. 运行 python Karrigell.py。 完事儿! 网站已经启动,界面已经拥有,输出的状态信息如图 CDay2-1 所示。 图 CDay2-1 Karrigell启动后输出的状态信息 组织 前后看了看,发觉默认的 Karrigell 发布 Karrigell-2.3.5/webapps 目录为网站根目录。 为了独立组织各种已有代码,在其中创建 cdc 子目录,先再将原先的 index.html 复制 一份到 cdc 中,然后修订默认首页 index.html,追加一链接:

PyCDC Web

刷新,如图 CDay2-2 所示: 图 CDay2-2 PyCDC 网站首页 CDays“光盘故事” | 59 呵呵,网页老实地显示出来,而且可以正常点击! 然后就是想法儿将 cdc/index.html 重构为 cdc/index.kss ,并可以调用原先 cdctools.py 中准备好的函式,能在命令行环境中干同样的活就得! 重构 先来个 20 多行: 1 # -*- coding: utf-8 -*- 2 def _htmhead(title): 3 '''默认页面头声明 4 @note: 为了复用,特别的组织成独立函式,根据 Karrigell 非页面访问约定,函式名称 前加"_" 5 @param title: 页面标题信息 6 @return: 标准的 HTML 代码 7 ''' 8 htm = """ 9 10 %s 11 """%title 12 return htm 13 ## 默认页面尾声明 14 htmfoot=""" 15
design by:Zoom.Quiet 16 powered by : Python + 17 KARRIGELL 2.3.5 18
19 """ 20 21 def index(**args): 22 print _htmhead("PyCDC WEB") 23 print htmfoot 刷新,如图 CDay2-3 所示: 图 CDay2-3 PyCDC网站 ks 实现 真爽!小白参考网络中搜索到的前辈的实例,一试即成!感觉真不是一般的爽快!而且60 | 实例故事 有两个小的技巧到手: 1. 在 Karrigell 中,函式名称有前缀"_" 的话,就会当作私密函式,不认为是可访问的 页面,凡是辅助性函式都可以这么来。 2. 直接使用 Python 的函式规范将 index() 的参数声明成动态参数**args,这样一来 将所有功能集成到首页中,当进行点击提交等操作时,index() 函式就可以智能地 接受不定长度、形式、格式的参数了! 导入 好了,将原先的工具脚本导入进来,就可以通过网页调用原先的功能了,但是,怎样才 能从 "CDays/cday2/Karrigell-2.3.5/webapps/cdc/index.ks" import "CDays/cday2/cdctools.py" 中复用已经成熟的功能呢?好像 import 操作不像网页的包含动作有相对路径可以玩 的?先不管了,复制过来一个,import!咦? 怎么看似导入成功了? from cdctools import * print dir() #通过检查名称空间进行测试,结果如图 CDay2-4 所示。 图 CDay2-4 import测试结果 瞧见了几个熟悉的函式名称,DONE! PS:小白万分感动地看到 Karrigell 提供了友好、丰富、明晰的网页调试信息汇报(如图 CDay2-5 所示)。 PCS109“系统参数”深入地 分享了在 Python 代码中可 以声明的各种参数形式,以 及适用情景。有兴趣的读者 可以深入体验一下 Python 这方面的 Pythonic。 PCS109 CDays“光盘故事” | 61 图 CDay2-5 网页输出调试信息 表单 好了,组织好了,接下来就是表单组织,最后连接上既有功能就得! HTMLTags:http://karrigell.sourceforge.net/en/htmltags.htm(精巧地址:http://bit.ly/fdz7w) 有说,Karrigell 提供一个非常OOP的支持模块,可以快速地像函式样儿地组织生成HTML 代码: print HTML( HEAD(TITLE('Test document')) + BODY(H1('This is a test document')+ TEXT('First line')+BR()+ TEXT('Second line'))) 将生成: Test document

This is a test document

First line
Second line 但是,可以进行交互提交的表单好像无法函式化的生成呀!有点郁闷,小白不想自个儿 折腾,根据“-1 day ”的经验: 你能够碰到的问题,99%的情况下其他人已经遇到过了。所以,最佳的解决方式就是找到 那段别人解决了相似问题的代码! 62 | 实例故事 还是问一声:“Karrigell 有表单快速生成模块吗?” 果然有行者反馈:Karrigell_QuickForm,网址为:http://wiki.woodpecker.org.cn/moin/KwDay3 (精巧地址:http://bit.ly/3VBSKI) FT!!连名字都一样:快速表单! 1 # -*- coding: utf-8 -*- 2 from Karrigell_QuickForm import Karrigell_QuickForm as KQF 3 #... 4 5 def index(**args): 6 '''默认主页 7 @note: 使用简单的表单/链接操作来完成原有功能的界面化 8 @param args: 数组化的不定参数 9 @return: 标准的 HTML 页面 10 ''' 11 print _htmhead("PyCDC WEB") 12 p = KQF('fm_cdwalk','POST',"index","CD Walk") 13 p.addHtmNode('text',"keywd","文件名",{'size':20,'maxlength':50}) 14 p.addGroup(["submit","btn_submit","Walk it!","btn"]) 15 p.display() 16 p = KQF('fm_cdwalk','POST',"index","CD Walk") 17 p.addHtmNode('text',"keywd","关键字",{'size':20,'maxlength':50}) 18 p.addGroup(["submit","btn_submit","Search It!","btn"]) 19 p.display() 20 print htmfoot 得了您呐!追加 8 行,完成两个功能针对性的表单,如图 CDay2-6 所示。 图 CDay2-6 使用 QuickForm 生成页面 搜索功能 随便输入,提交,没有任何问题,但是怎么获得提交的信息? CDays“光盘故事” | 63 在网上 http://karrigell.sourceforge.net/en/programming.htm(精巧地址:http://bit.ly/2z6wju) 有说。瞧着这 QUERY 内置变量就是亲切啊! 追加: if 0 == len(QUERY): pass else: print QUERY print htmfoot 再提交两次,彻底明白了网页参数的传送,绑定原先的功能! ## 全局变量 CDCPATH = "/path/to/LovelyPython/CDays/cday2/cdc" CDROM = '/media/cdrom0' ... if 0 == len(QUERY): pass else: if "Search" in QUERY['btn_submit']: print I("try search *.cdc for KEY:%s"%QUERY['keywd']),BR(), cdcGrep("%s/"%CDCPATH,QUERY['keywd']) elif "Walk" in QUERY['btn_submit']: iniCDinfo(CDROM,"%s/%s"%(CDCPATH,QUERY['keywd'])) else: pass print htmfoot 追加 6 行,完成核心功能绑定! 运行结果如图 CDay2-7 所示: 图 CDay2-7 搜索功能演示 PCS100“import”进一步说 明了模块和包使用的方法和 注意事项。 PCS100 64 | 实例故事 成咧!! 呵呵,一切太自然了…… 搜索结果“二传” 不过,这搜索匹配结果的输出也忒糙了点儿,随手改进一下吧: 1 # -*- coding: utf-8 -*- 2 3 def cdcGrep(cdcpath,keyword): 4 export = "" 5 filelist = os.listdir(cdcpath) # 搜索目录中的文件 6 for cdc in filelist: # 循环文件列表 7 if ".cdc" in cdc: 8 cdcfile = open(cdcpath+cdc) # 拼合文件路径,并打开文件 9 export += cdc 10 for line in cdcfile.readlines(): # 读取文件每一行,并循环 11 if keyword in line: # 判定是否有关键词在行中 12 #print line # 打印输出 13 export += line 14 print export 呜乎!依然没有反应,页面输出的居然是 None?!咦?这是什么意思? 好吧,使出调 试的终极招术:重启服务!Ctrl+c 键或是直接关闭运行脚本的命令窗口,再次运行 python Karrigell.py。Bingo! 再次搜索,输出了修订后的结果列表! 小白进一步想了想,在搜索时总是会有重复的搜索,怎么样可以将每次的搜索成果记录 下来,以便日后进行分析或是直接复用呢? 那么,二传一个! 其实想用用超级 cool 的模块 pickle(序列化! 对象腌制器!)据说可以将 Python 的数 据对象输出成文本文件,而且导入时直接转化成自然的对象!!! 1 # -*- coding: utf-8 -*- 2 3 def cdcGrep(cdcpath,keyword): 4 expDict = {} 5 filelist = os.listdir(cdcpath) # 搜索目录中的文件 6 for cdc in filelist: # 循环文件列表 7 if ".cdc" in cdc: 8 cdcfile = open(cdcpath+cdc) # 拼合文件路径,并打开文件 9 expDict[cdc]=[] 10 for line in cdcfile.readlines(): # 读取文件每一行,并循环 PCS210“pickle”进一步分 享了这一常用模块的特性。 最可爱的,就是这个模块支 持自然的对象到文件对象的 双向转换! PCS210 CDays“光盘故事” | 65 11 if keyword in line: # 判定是否有关键词在行中 12 #print line # 打印输出 13 expDict[cdc].append(line) 14 pickle.dump(expDict,open("searched.dump","w")) 15 16 if __name__ == '__main__': # this way the module can be 17 '''cdctools 自测响应处理 18 ''' 19 CDROM = '/media/cdrom0' 20 CDCPATH = "/path/to/LovelyPython/CDays/cday2/cdc/" 21 ## 需要根据实际情况指向真实的目录 22 cdcGrep(CDCPATH,"EVA") 23 print pickle.load(open("searched.dump")) cdctools.py 运行结果如图 CDay2-8 所示。 图 CDay2-8 cdctools.py 运行结果 对应地导出文件 searched.dump: (dp0 S'z.MCollection.29.cdc' p1 (lp2 sS'mfj-00.cdc' p3 (lp4 sS'MCollec.39.cdc' p5 (lp6 sS'z.Animation.00.cdc' p7 (lp8 S'[L:\\mAnimi\\Gainax\\EVAalbumESP]\r\n' p9 asS'z.MFC.pop.02.cdc' p10 (lp11 s. 果真可以从纯文本直接变成字典对象呀,Cool! #完善首页代码为:... 66 | 实例故事 def index(**args): ... if 0 == len(QUERY): pass else: if "Search" in QUERY['btn_submit']: if "" == QUERY['keywd']: print H3("pls import SearchKey!") else: print I("try search *.cdc for KEY:%s"%QUERY['keywd']) print BR()#,cdcGrep("%s/"%CDCPATH,QUERY['keywd']) cdcGrep("%s/"%CDCPATH,QUERY['keywd']) searcheDict = pickle.load(open("searched.dump")) for cdc in searcheDict.keys(): print H5(cdc) for line in searcheDict[cdc]: print BR(line) elif "Walk" in QUERY['btn_submit']: print I("try walk CD for:%s ..."%QUERY['keywd']) iniCDinfo(CDROM,"%s/%s"%(CDCPATH,QUERY['keywd'])) print BR(),B("had export info. file as::%s/%s"%(CDCPATH,QUERY['keywd'])) else: pass print htmfoot 搜索时信息显示如图 CDay2-9 所示: 图 CDay2-9 搜索时信息显示 CDays“光盘故事” | 67 记录时信息显示如图 CDay2-10 所示: 图 CDay2-10 记录时信息显示 TODO Karrigell 这么听话,几乎是随想随写就完成了 Web 化的界面实现,接下来? 美化! 呵呵,这在 HTML 中比在 GUI 的各种框架代码中可以要 Easy 多了! CSS 而已。 web.py web.py 是绝对简单直接的应用框架,但是同样拥有各种高级特性! 核心文件就两个完整的最精简 Web 应用框架,虽然现在也发展到有十来个文件,但是也 包含了从 DB 到模板到 Web 服务林林总总的所有高级 Web 应用特性。 不过坚持了最 Pythonic 的直接表述原则,小白看到完整功能网站实例就 8 行: 1 import web 2 urls = ('/(.*)', 'hello') 3 class hello: 4 def GET(self, name): 5 i = web.input(times=1) 6 if not name: name = 'world' 7 for c in xrange(int(i.times)): print 'Hello,', name+'!' 8 if __name__ == "__main__": 9 web.run(urls, globals()) 68 | 实例故事 直接调用 python tryweb.py,如图 CDay2-11 所示。 图 CDay2-11 web.py 框架使用初探 就会在本地 8080 端口发布一个可以响应用户 URL 输入的网站!不过,实在太简单了, 什么都要自个儿来,不如 Karrigell 做的舒服呢! 小结 在本日故事中,小白通过响应其它小白的新界面需求,探索了一下 Web 应用框架,照例 在行者的提点下开始了首个轻量级框架 Karrigell 的使用;至少可以体验到: 1. 在桌面,本地网站是可以视作用户软件界面的! 2. 一个可用的 Web 应用框架,至少应该包含直观的模板系统,方便的自动更新/调 试支持。 3. 永远有辅助性的第 3 方模块可以加速我们想法的实现,不过,要付出努力去寻找。 到现在,小白已经无须学习什么新的 Python 语法点了,一切已经足够小白所关注需求的 快速实现,甚至于可以直接阅读他人的代码来理解既有模块的功能了……Python 的易学、 易用、易读的特性可见一斑! 练习 1. 如果在 Karrigell 实例中,不复制 cdctools.py 到 webapps 目录中,也可以令 index.ks 引用到? 2. 经过本章对 Karrigell 的初步学习,实现一个简易的 Web 留言系统。主要利用 Karrigell_QuickForm 实现提交留言并显示出来。 3. 思考本日提出的搜索结果积累的想法,如何实现? 如何在搜索时可以快速确认以前 曾经搜索过相同的关键词,直接输出原先的搜索成果,不用真正打开 CD 信息文件 来匹配? CDays“光盘故事” | 69 CDay+3 优化!多线程 应用多线程再次优化 KISS 才是王道! 盘点功能 小白又在自满地清点着到目前为止已经自主实现的所有功能了。 1. 一个基于命令行的界面,可以: 1) 将光盘内容索引存储为硬盘上的文本文件。 存储成*.cdc 的文本文件; 可以快速指定存储目录; 可以快速指定文件名。 2) 根据储存到硬盘上的光盘信息进行搜索。 可以根据指定关键字进行匹配搜索; 可以搜索指定目录中所有*.cdc 文件。 2. 一个基于 Web 网页的界面,同样可以: 1) 将光盘内容索引存储为硬盘上的文本文件。 存储成*.cdc 的文本文件; 可以快速指定文件名。 2) 根据储存到硬盘上的光盘信息进行搜索。 可以根据指定关键字进行匹配搜索; 可以搜索指定目录中所有*.cdc 文件。 70 | 实例故事 已经超额完成了当初想要实现的所有功能了!但是,人生不如意者,十之八九也! 为什么这么慢 在小白兴高采烈地将过往百多张 VCD,几十张 DVD 都列成 *.cdc 摘要文件通过 PyCDC 管理起来后,发觉,在搜索时真的非常的慢啊! 列表中也有人在吼! “俺就是将硬盘备份 DVD 导入了一下,10 万左右个文件,5000 左 右的目录,怎么搜索起来这么慢啊!” 这问题就严重了,小白当然不认为是 Python 天生慢,看着就几行相关的代码,他也知道 是自个儿的功力不够,根本没有想过执行效率的问题。 好吧,该优化时就优化,没有什么不好意思的! 不过,咋整呢? 分析 经过不断的重构,反思,小白已经非常习惯先分析,再找现成代码来偷懒的“开发”模 式了: 原先对搜索速度没有感觉,现在却感觉很慢,唯一变化的是文件数量和总体积。 影响速度的行为应该有: 1. 打开文件 2. 文件读取 3. ini 解析 4. 搜索匹配 5. 输出 以上元素,除了搜索匹配,其他都是使用内置模块实现的,基本没有效率问题。但是, 搜索匹配也是简单匹配,几乎没有什么好改进的啊!唉呀呀!晕!小白突然想到,整个 流程中,是按照过滤出的文件名进行的,每一个文件都要走上述所有过程后才轮到下一 个,可以说强大的计算机几乎一直在等待简单地输入输出后,再处理下一个文件! CDays“光盘故事” | 71 并发处理 让电脑同时搜索多个文件不就好了么?! 进程 Vs 线程 但是!并发什么来处理? 小白也道听途说过什么进程和线程的,那么使用什么方式好呢?没有头绪呢,吼吧! 呵呵,看来是吼到点子上了,列表中行者们的回复丰富得很! “进程是操作系统可以调配的最小资源集合!” “Python 支持多线程的,干什么不用?” “线程才是操作系统可以调配的最小资源单位!” “一个程序至少有一个进程,一个进程至少有一个线程。” “一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 但是多个进程中的不同线程很难交互数据……” 晕了,彻底晕了,这都在说些什么呀? 小白自个儿参照着做了点研究,确认: 1. 进程和线程就是程序执行时请求的系统不同级别资源的名称; 2. 多进程就像孙悟空猴毛变的多个分身,大家自个儿干自个儿的,牵扯较少; 3. 多线程就像哪吒的三头六臂,一个人同时干多个事儿,还能相互配合。 那当然是多线程了!毕竟搜索结果是得集成在一起输出汇报的呀! KISS KISS == Keep It Simple,Stupid 在列表中,小白记住了 KISS 原则,不论什么,坚持简单的过程/原理/结构/代码,就是自 在!所以,参考模块手册( thread-objects),找个实例代码:Python 线程编程的两种方式 (作者:guanjh)。 照猫画虎准没大错! PCS206 “thread”详细说 明了线程支持模块的基本使 用方法。 PCS20672 | 实例故事 实现 用 threading 模块的类组织实现,比用 thread 模块中的线程函式更加容易掺入不同的处 理过程,就用 threading 模块了! 按例改造 cdctools.py,追加准备进行并发搜索的线程对象: 1 # -*- coding: utf-8 -*- 2 from threading import Thread # 导入线程支持模块 3 class grepIt(Thread): 4 def __init__ (self,cdcfile,keyword): 5 Thread.__init__(self) 6 self.cdcf = cdcfile 7 self.keyw = keyword.upper() 8 self.report = "" 9 def run(self): 10 if ".cdc" in self.cdcf: 11 self.report = markIni(self.cdcf,self.keyw) 呵呵,忒简单了,就几行,而且,实际行为就一个!markIni() 也是改造自原先的 cdcGrep()。 1 # -*- coding: utf-8 -*- 2 def markIni(cdcfile,keyword): 3 '''配置文件模式匹配函式: 4 ''' 5 report = "" 6 keyw = keyword.upper() 7 cfg = rcp() 8 cfg.read(cdcfile) 9 nodelist = cfg.sections() 10 nodelist.remove("Comment") 11 nodelist.remove("Info") 12 for node in nodelist: 13 if keyw in node.upper(): 14 print node 15 report += "\n %s"%node # error as "\n",node|str(node)... 16 continue 17 else: 18 for item in cfg.items(node): 19 if keyw in item[0].upper(): PCS207“threading”详细 分享了高级线程模块的基础 应用。 PCS207 CDays“光盘故事” | 73 20 report += "\n %s/%s "%(node,item) 21 return report 在末尾追加实际搜索调用,并声明成模块测试: 1 # -*- coding: utf-8 -*- 2 def grpSearch(cdcpath,keyword): 3 '''多线程群体搜索函式: 4 ''' 5 begin = time.time() 6 filelist = os.listdir(cdcpath) # 搜索目录中的文件 7 searchlist = [] # 记录发起的搜索编程 8 for cdcf in filelist: 9 pathcdcf = "%s/%s"%(cdcpath,cdcf) 10 current = grepIt(pathcdcf,keyword) # 初始化线程对象 11 searchlist.append(current) # 追加记录线程队列 12 current.start() # 发动线程处理 13 for searcher in searchlist: 14 searcher.join() 15 print "Search from ",searcher.cdcf," out ",searcher.report 16 print "usage %s s"%(time.time()-begin) 17 if __name__ == '__main__': 18 '''直接利用模块测试部分,先检验是否正确可用 19 ''' 20 grpSearch("cdc/","EVE") 这样,就可以直接在命令行中运行 python cdctools.py ,测试其是否可用了,而且根 据看到过的代码随手追加了运行时间汇报,可以输出搜索总用时,运行结果如图 CDay3-1 所示: 图 CDay3-1 cdctools.py 改进后运行结果 74 | 实例故事 TODO 啊!主体功能摸索完毕!然后呢?然后就顺势而为了: 1. 修订命令行界面的代码配合; 2. 修订 Karrigell 的 Web 桌面界面的代码配合; 进一步优化速度还可以怎么整呢?缓存 ini 的对象化解析?! 不用每次每个.cdc 文件都重 新解析一回。缓存搜索过的关键字结果?! 有相同的搜索就不用再来一遍了。缓存…… 看起来可以做的还有很多,而且,可以想到这些,小白已经不是小白了! 可喜可贺! 小结 在本日故事中,小白由实现大规模使用原创工具的真实体验中引发了又一轮重构。针对 执行效率的重构,就像玩头脑急转弯,只要充分直觉地分析运行时的整体软件行为,很 容易确定瓶颈。 对问题有了准确的理解后,再向行者请教时便有了确切方向,进而容易获得有效的提示; 不过,的确有些电脑的基础 FAQ 是回避不了的,在理解了进程和线程后,Python 内置的 模块就可以解决了: 1. threading 模块是个易用好用的线程支持模块,可以快捷地将合适的行为并发化。 2. 多线程的调试一定要设计有合理明确的输出信息,以便确认哪里有问题。 练习 1. 熟悉线程相关知识后,利用 Lock 和 RLock 实现线程间的简单同步,使得 10 个线程 对同一共享变量进行递增操作,使用加锁机制保证变量结果的正确。 2. 使用 Queue 实现多线程间的同步。比如说,10 个输入线程从终端输入字符串,另 10 个输出线程依次获取字符串并输出到屏幕。 3. Python 中的 Event 是用于线程间的相互通信的,主要利用信号量机制。修改题 1 的 程序,利用信号量重新实现多线程对同一共享变量进行递增的操作。 CDays“光盘故事” | 75 CDayN 基于 Python 的无尽探索 想象力才是 Pythoner 的唯一界限! 需求的遐想 从最初的需求到今天,小白已经独立完成了至少以下功能。 1. 一个基于命令行的界面,可以: 1) 将光盘内容索引存储为硬盘上的文本文件。 存储成*.cdc 的文本文件; 可以快速指定文件名。 2) 根据储存到硬盘上的光盘信息进行搜索。 可以根据指定关键字进行匹配搜索; 可以搜索指定目录中所有*.cdc 文件。 3) 同时为每个摘要文件发出独立线程来并发地进行关键字搜索。 2. 一个基于 Web 网页的界面,同样可以: 1) 将光盘内容索引存储为硬盘上的文本文件。 存储成*.cdc 的文本文件; 可以快速指定文件名。 76 | 实例故事 2) 根据储存到硬盘上的光盘信息进行搜索。 可以根据指定关键字进行匹配搜索; 可以搜索指定目录中所有*.cdc 文件。 3) 同时为每个摘要文件发出独立线程来并发地进行关键字搜索。 所有需求都在 50 行代码以内就实现了核心功能,小白感到非常欣慰,经过清点代码,真 正地对 Python 树立起了足够的自信! 他这才对社区里 Python 简介中的开场诗有了全面的理解: 左咖啡,右宝石;还是灵蟒最贴心! 最贴心,不费心,用好只须听故事。 想清楚,就清楚,一切自己来动手! 要清爽,常重构!刚刚够用是王道! 然后结合各方信息开始尝试。 桌面化 一定要启动个 Web 服务器才能看到漂亮的界面吗?一定要忍受简单的字符命令行界面 吗?自然是可以进化的!Python 可以使用的 GUI 框架也不少: Tk/Tcl Qt WxPython win32com 还有其他一些框架,甚至可以使用 mozilla 框架的 XUL 接口来利用 FireFox 的界面生 成软件,只须选择一个简单有趣的来尝试就好。 FP 化 一定要 OOP 开发吗? 现在的 CDC 仅仅是接受光盘信息和查询关键字,如果从面向数据的开发考虑,完全可 啄木鸟社区专精于 Python 技术在中国的应用和推广, 收集有大量相关资料,也有 专用的推广文章: http://wiki.woodpecker.org. cn/moin/SpreadPython (精巧地址:http://bit.ly/ 16xO0W),其中的“Python 简介”(http://www.zoomquiet. org/share/s5/intropy/070322 -introPy/精巧地址:http: // bit.ly/3suMWb)是常用的 “忽悠”Python 的入门手册。 PCS217 “ Tkinter ” 中 的 Tkinter 是 Python 中一种比 较流行的图形编程接口。 Tkinter 模块是 Python 的标 准 Tk GUI 工具包的接口。 TK 和 Tkinter 是为数不多的 跨平台的脚本图形界面接 口。 PCS217 PCS302“Leo”作为纯 Python 实现的轻巧文学化 编辑环境,已经演化成了文 学化应用平台,各种插件层 出不穷,Leo 本质上只是 Tk 的一个跨平台文本编辑软 件。但是由于其独特的文本 处理视角,引发了各种应用, 这里快速分享了 Leo 的世界 观,引导读者进一步探寻文 学化编辑的世界。 PCS302 CDays“光盘故事” | 77 以跃迁成基于 FP 的函式型程序。一切都是函式间的嵌套调用,一旦发生问题就会自然退 出,无模式,无状态,只是单纯的数据响应,整个结构可以更加扁平不易出错,好维护。 Python 同样内置有一些标准的 FP 算子: lamba() map() zip() apply() reduce() filter() 更加魔幻的是在 Python 2.4 以后追加了“修饰符”(@)。可以通过更加简单的方式进行 函式叠加了, Python 有能力将自个儿写成 Lisp 一样的纯函式程序,不过,除了 Cool 一 时想不出有什么具体的好处。 C 化 对速度和内存有洁癖的人,是否可以使用 Python 来快速开发,却可以得到 C 样的最高 效执行效能? Pyrex 是一种用来编写 Python 扩展模块的语言。简单地说,就是可以使用 Python 来完 成功能,然后通过 Pyrex 生成同样功能的 C 代码,以供编译生成最高效执行程序! 而且也有其他各种可以渗入 C/C++的支持模块: ctype boost cxx WarpPy SWIG SIP 想 C 化 Python 应用也只是几分钟的事儿! PCS114 “FP 初体验”进一 步说明了内建的一些函式化 算子的使用,帮助读者体验 什么是 FP~函式化编程,这 是种古老而且高效易维护的 编程思路,虽然和主流的命 令序列式编程体验完全不 同,但是真的是非常值得学 习和理解的好东西。 PCS114 78 | 实例故事 Flash 化 Flash 可以说是现在最漂亮的界面创造方式了, Python 应用可以使用 Flash 作前端吗? 随便看了看 Flash 的 ActionScript 脚本,发现非常像 JS,而且可以通过 XML 进行交互, 好的 Python 的 XML 解析和处理也不是摆设。 完全可以通过 Flash 提供美丽动态的界面,由 AC 组织好事务 XML,丢给 Python 处理 实际数据,然后依然是 XML 返回,达成软件的可用性。 只是,这其中还是需要个标准的 Web 服务器提供给 AC 进行 URL 访问,好在 Python 自 行创立个简单的 Web 服务也实在不算什么,利用现成的 Karrigell/web.py 等超轻量级 Web 应用框架也一样轻松。 分布化 好的,如果我们的光盘多到无法在一台机器保存所有摘要信息,或是需要有不同的服务 器来分类保管,或是需要同步收集 DVD 信息,Python 是否也可以支持?一查才知道,哗! 世界第一的 BT 下载体系——bittorrent 根本就是 Python 开发的!而且类似 EVE 的大型网 络游戏也是使用 Stackless Python 进行开发的。那么,分布的话也应该没有问题,关键是 分布到什么程度。 小白期望: 可以自动将不同主机上的实例收集的 DVD 摘要信息相互同步到本地。 可以自动分发搜索要求,并将各自的结果汇总到任何一台发出查询请求的主机处。 压缩化 现在的摘要信息是以 .ini 的格式存储在文本文件中的,如果多了有些浪费空间,是否可 以像其他软件那样弄个自己的压缩格式? Python 内置支持从 .zip 文档中导入数据对象?! 好吧,不用努力就可以获得的特性,soooooo easy! CDays“光盘故事” | 79 定制化 操作的命令,或是页面上的元素、输出的方式,是否每个人都可以自行调节呢? 这和 Python 没有直接关系,是 CSS、Ajax 的事儿了,不过,只要是通过 XML 等标准数 据沟通的模式,Python 就是没有问题的! 智能化 现在的查询实在忒简单了点,我要像 Google 那样的组合条件!要有正则表达式的模糊模 式匹配!呵呵,正则表达式是内置在 Python 中的,组合条件不过是参数的解析,不应该 是个问题吧? Google 化 想协助地球上所有其他有光盘管理需求的人们使用 PyCDC 进行轻便地光盘仓库管理? 使用 GAE (Google App. Engine )服务! 透过 Google 的免费应用发布环境! 透过 Google 的强力搜索引擎服务! 使用 Google 的轻型分布式数据仓库! 借助 Google 的无限空间邮箱服务! 组合 Google 的其他各种免费强力服务! 将 PyCDC 变成依托 Google 公司的全球化服务吧!唯一的要求是你会 Python。 我化 还想将小白的学习成果 PyCDC 怎么折腾?读者自个儿来吧,没有人可以比你更加理解你 的需要,深入学习 Python“我化”这一小工具好了。 想象力才是 Pythoner 的唯一界限! PCS400 “GAE”简要地介 绍了 2008 年 4 月刚刚发布 的 Google App. Engine 服 务,分享了在 GAE 中快速建 立自个儿原创应用的方式/ 方法/思路。 PCS400 80 | 实例故事 小结 充分信任 Python,大胆设想,小心求证,搜寻己有实例,谁都可以创造出美妙实用的工具! KDays“Web 应用故事” | 81 KDays“Web 应用故事” KDay0 Web 开发启航 82 KDay1 品尝 KarriGell 86 KDay2 通过表单直接完成功能 93 KDay3 使用第 3 方模块规范化表单 101 KDay4 使用 KS 模式重构代码 114 KDay5 通过 session 重构应用流程 123 KDay6 利用 mm 人性化组织成员信息 135 KDayN 经验总结,畅想 Web 应用 147 82 | 实例故事 KDay0 Web 开发启航 定场诗 前樱桃,后涡轮,还是推车最贴心。(CherryPy TurboGears KarriGell) 最贴心,不省心,一切头绪要理清。 想清楚,就清楚,一切清楚才清爽! 要清爽,常重构!才有更爽在后头! 以上是小白通过了 CDays 的历练后从某位行者那儿瞧到的打油诗,拿来作这篇实例故事 的开场白。这是因为,在 Python 的帮助下,实现了 CDCollector 的大部分功能之后,小 白真切地感受到了这首诗所传达出的那种感慨。 缘起 在工作学习之余,小白努力想将 Pythonic 的体验分享给同事和同学们,以至上司和老师 们都知道有个 Python 脚本语言可以快速完成各种任务。所以,一有事他们首先就想叫小 白来尝试一下。这不,小白接到了个比较正式的开发任务——改建一个简单的在线问卷 系统,可参考现已使用的一个简单的在线问卷。 小白看了一下源代码,感觉非常简单,就自发地将任务转变成开发一个简单的在线问卷 管理系统(暂定名为“EasyPaper)”,可以方便地创建简单的问卷页面出来。 KDays“Web 应用故事” | 83 小白主动地将这次基于 KarriGell 的 Web 应用开发过程记录成了实例故事,以连载的方式 分享给社区。 不过,小白自知不是什么写书能手,只是想分享 Python 开发的快捷体验,没有经验和自 信能与没有任何 Pythonic 体验的初学者产生共鸣,所以,小白设定了故事准入条件—— 想看接下来的实例故事的人,须满足以下条件: 1. 了解互联网; 2. 了解 HTML; 3. 了解 CSS; 4. 了解 DHTML; 5. 了解动态页面的含义; 6. 了解 PHP 或是 ASP 的开发思路; 7. 了解 Python; 8. 了解 CherryPy。 所谓了解,就是对以上方面技术:明了相关基础知识,看过/写过相关代码,知道具体是什 么性质和范畴的知识;否则,读者一定会在第一时间感到困惑,进而倍感挫败的。 另外,本故事面向那些控制欲强——对于任何系统,如果不是所有代码都是自个儿写的, 会感觉非常不靠谱,不敢使用的那种人,他们可能会比较喜欢这篇故事,否则,对所谓 “成熟框架类”体系非常崇尚的,期望在大量优雅的自动生成的框架代码帮助下快速完 成开发的人,一定会感觉这样开发太别扭。 故事 角色 小白: 已经不是菜鸟的小白,以第三人称的方式,记述了自个儿在实例开发活动中的各种体验。 行者: 啄木鸟/CPyUG/ZEUUX 等等中国活跃技术社区的那群热心的 Python 用户,在 Python 应 用/学习方面是先行者,但都不是专业教师,所以,说话可能有些颠三倒四,但都是真心 助人的好人。 PCS300“CherryPy”分享 了樱桃蟒 这一最早出现的 关注 OOP(面向对象编程)体 验的应用框架,基础使用体 验。 PCS300 PCS401“DHTML”分享了 动态 HTML 这一常用网络 应用技术的知识点,以及连 带涉及 PHP/AS 等动态网页 技术。 PCS401 84 | 实例故事 约定 1. 每节故事都提供可运行的 KarriGell 实例站点脚本,以便通过实际运行,得到直观的 体验。 注意:所有实例代码使用 SVN 提供下载,访问 URL 地址为: http://openbookproject.googlecode.com/svn/trunk/LovelyPython/KDays/ 精巧地址:http://bit.ly/2aAaUy SVN 是 Subversion 的简称,是种非常流行和稳定的版本管理系统;简单讲,就是一 个中央服务器及配套工具集;可以方便地管理所有开发时的各种文件;神奇的是, 它可以帮助你安全地取回任何时间点上的文件/目录——即使文件/目录曾经被删除 过。进一步的介绍公布在 http://wiki.woodpecker.org.cn/moin/SubVerSion 上。 精巧地址为:http://bit.ly/b7w9K 2. 实例故事,不是教程,小白只会讲述要点,具体的都含在脚本代码中了,读者只要 下载运行,尝试按照自个儿的理解修订一下,看一看运行的反应,就可以深入到系 统的各个层面,理解其细微之处。 3. 实例脚本全部使用 Leo 组织!小白也推荐读者可以尝试通过 Leo 来观察代码,这样 可以感受到小白组织代码时的思路,也可轻松掌握代码的整体框架。 4. 每日故事涉及的开发,基本上都是在 3.1415 小时之内就可以完成的。 5. 每日故事最后的小结,是小白养成的好习惯:“及时清点成果或是问题,同时给明日 的开发定出可行的目标。” 6. 每日故事最后的练习,是小白根据故事涉及的知识点和领域技术所设计的一些问题, 列出来是期望读者独立进行尝试,以强化相关体验。 习题解答发布在图书维基: http://wiki.woodpecker.org.cn/moin/ObpLovelyPython/LpyAttAnswerKdays 精巧地址:http://bit.ly/axi7 用 SVN 下载: http://openbookproject.googlecode.com/svn/trunk/LovelyPython/exercise/part2-KDays/ PCS302 “Leo”中指出 Leo 是种极具个性的文学化编辑 环境,使用独特的方式来组 织我们的软件工程,在 PCS302 分享了初步体验。 PCS302 KDays“Web 应用故事” | 85 目标 小白最终的开发目标,就是使“EasyPaper”工具能作到以下功能: 可以通过有格式约定的纯文本生成问卷; 可以批量生成问卷; 生成的问卷可以立即受理登录和回答; 问卷的回答可以得到实时的成绩统计; 可以随时修订格式约定的纯文本,从而变更已经发布的问卷。 接下来给大家看一个实例问卷定义文本样例。 #easy051201.cfg [desc] pname = 啄木鸟问卷 之 “基本知晓” desc = 自学问卷 v0.7 learn = CPUG 首页 # 问卷状态: 0 设计中|1 发布中|2 发布过 done = 0 [ask/1] question= 啄木鸟社区首页在哪里? a = woodpecker.org.cn b = python.cn c = 不知道…… key = a # 正确答案 [ask/2] …… 提示:文本支持注释,约定凡是 # 开头的行,就略去,不用作问卷生成。 练习 尝试运行 Karrigell,其下载地址为:http://sourceforge.net/projects/karrigell(精巧地址: http://bit.ly/1i7NyI)。 下载 Karrigell 解压; 在终端中进入 Karrigell 所在目录; 运行 python Karrigell.py,看看出现什么? 再在浏览器中输入 localhost,再看看出现什么? 如果出现意外,尝试根据屏幕提示进行排除。 86 | 实例故事 KDay1 品尝 KarriGell 快速根据现成的 PHP 实现思路,尝试 Karrigell 的开发 背景 一般的问卷系统其实就是一组手工写成的非常简单的 PHP 页面,每个页面包含一个选择 题,点击“下一题”就可以将每个人对应的回答记录到数据库中,其中: 每页头部包含前页题目的正确回答; 由 PHP 通过 URL 参数判定回答是否正确,并将前一题的回答情况记录到数据库中; 然后通过数据库的查询进行成绩统计。 小白学习了 Python 后,首先写了个脚本,可以从 .ini 文本中自动批量生成相似的 PHP 页面: 每个类似 [ask/1] 领头的一节内容包含一系列键值对,对应生成一个选择题及其答 案; 其中在 key= 一项记录了正确答案的选择项。 然后配套也写了自动查询 DB,统计出回答问卷的成员成绩的 Python 脚本,所以,现在 的问卷生产流程是: .ini(txt) -> Python -> PHP -> DB -> Python -> 答巻统计 这样的数据流程真够傻的,经过了太多手续,使用了太多的语言和中间处理,而且无法 作到答卷成绩的立等可取! PHP ( Personal Hypertext Preprocesser,个人超文本 解析)语言是种专门针对互 联网应用设计的快速功能型 语言,对于网站开发有独到 的处理思路,但是不属于本 书分享的范畴,请读者自行 搜集相关文档学习, TisonG!⋯⋯ KDays“Web 应用故事” | 87 准备环境 总算得空,小白立即想尝试使用纯 Python 重构出一个可快速修改+统计成绩的简单问卷 系统。不过,为什么选择 KarriGell 呢? 小白想这么回答:“缘分吧……呵呵!”真实原因就是“懒”,毕竟在前述 CDays 实例 故事中,在 Python 学习过程中,小白已体验过了 KarriGell,当然,就首先使用熟悉了, 哈哈哈! 立即开始! 下载 KarriGell,一般是个单独的压缩文件,比如说 Karrigell-2.4.0.tar.bz2。 下载地址:http://sourceforge.net/projects/karrigell 精巧地址:http://bit.ly/1i7NyI 接着,使用工具解开压缩(推荐使用 7-zip 这一自由软件,通吃一切常见压缩包! 网址 为:http://7-zip.org/zh-cn/)。 然后,进入 KarriGell 所在目录, 将 conf/Karrigell.ini 中的#port=8081 去掉注释,表示将 网站发布到 :8081 端口,避开用户可能的己有默认:80 网站。 最后,在命令行下面输入:python Karrigell.py。M$用户的命令行环境调用: 开始→命 令→输入“cmd”(或是你习惯的任何 M$终端软件);Unix/Linux 用户则调用:应用程 序→附件→终端(或是其他任何你喜欢的终端软件)。 bingo!通过 http://localhost:8081/ 小白就已经获得了一个展示有各种应用的 KarriGell 站 点了!其默认首页如图 KDay1-1 所示。 图 KDay1-1 默认首页 PCS404“代码重构浅说”中 谈到的重构(Refactoring) 是现代软件开发工程中常用 技巧,可以保证系统在可用 状态下持续不断地提高代码 品质!在 PCS404 中,浅显 地说明了一下重构思想及常 见的重构技巧。 PCS404 PCS301 “Karrigell”分享 了这一被选中的号称最简易 好用的应用框架的基本配置 和开发使用体验;就在本书 前一部分,CDays 故事中, 最后小白就是使用的这种框 架。 PCS301 88 | 实例故事 这也意味着,只要有 Python 环境,我们可以在任何平台中,随时获得一个全功能的 Web 应用环境! 动手动脚 跟着教程走,随便动一动,在默认的 KarriGell 站点中,就含有教程(如图 KDay1-2 所示)! http://localhost:8081/demo/frame_tour_zh.htm 是最简要的入门教程,展示了 KarriGell 进行 Web 开发中最经常要面对的应用实现。 图 KDay1-2 内置中文教程 小白也算修炼过 Python 编程的,当然对于“hello world”这种无用代码已没有兴趣了, 想直接进行交互式的 Web 应用开发。经过行者的点拨,注意到有种 .pih 发布方式,类 似原先的 PHP 页面,可以将执行代码嵌入 HTML 页面中。 根据内置教程的演示,小白直接对原有页面 http://localhost:8081/demo/test.pih 进行了修改:

HTTP headers

<% a="Karrigell" print dir(a) %> 呵呵!!页面输出(如图 Kday1-3 所示)果然和命令行中的反应一样,看来就将 KarriGell 想象为面向 Web 的 Python 解释器好了! KDays“Web 应用故事” | 89 图 KDay1-3 打印 dir()的页面输出 组织开发环境 “OK!我期望可以随时看到教程,同时又有独立的站点空间……”小白这样想着的时候, 机缘巧合下学习了 Leo ,于是他认为一切都应该由 Leo 组织才自然。那么就按照习惯 来定制 KarriGell 吧! 其实 KarriGell 的设置是非常简单的,由统一的 ini 文件来设定整体行为。我们现在的 需求非常简单,就是将 KarriGell 的默认教程站点和小白自个儿的开发隔离开,以便开发 的同时不影响随时参考内置教程,所以查找有关目录别名的配置……我们在 ini 文件找到 以下代码。 [Alias] ... # demo=%(base)s/demo doc=%(base)s/doc debugger=%(base)s/debugger Yeah!就是这样,非常直观,那么定义一个自个儿开发的站点目录在 KarriGell 系统目 录中就好。呵呵,这里要感谢 Unix 系统中的 link 命令!小白可以将开发中的问卷系统 文件放在任何习惯的目录中,仅仅链接到 KarriGell 的环境目录中就完成了自个儿的应用 安装; 如果在 M$ Windows 环境中就只能在 KarriGell 的环境目录中建立目录了。 比如说,在 Linux 中,小白的 EasyPage 工程组织在: ~/LovelyPython/KDays 90 | 实例故事 而下载安装的 Karrigell 在: ~/0penproj.s/trunk/karrigell/trunk 则小白只要运行: $ ln -s ~/LovelyPython/KDays/kday1 \ ~/0penproj.s/trunk/karrigell/trunk/webapps/ 就等于在 ~/0penproj.s/trunk/karrigell/trunk/webapps/ 中安装好了~/LovelyPython/KDays 中的 kday1 目录。然而若在 M$ Windows 环境中,小白就只有将 kday1 目录复制到 KarriGell 的环境目录下了。这样,小白想灵活地将不同目录中的不同工程目录配置到不 同版本的 Karrigell 实例环境中,是无能为力的。 首先,习惯性地在 Leo 中设立好自个儿的工程,组织一个最简单的 index.pih: <% a="Kariigell" print dir(a) %> 为了方便,使用 Leo 在节点声明 @nosent 操作符,以便按下 Ctrl+S 时,就自动在指定 的目录里保存相应文件(如图 KDay1-4 所示)。 图 KDay1-4 Leo 界面中的情景 对应地,在配置文件 Karrigell.ini 中设定一下。 [Alias] ... k=%(base)s/webapps/KDays ... OK!重启一下 KarriGell 。这里试用一下 Linux/Unix 系统中的通用操作 Ctrl+C,令 KarriGell 的 Python 进程停止,然后再运行 python Karrigell.py。其后访问地址: http://localhost:8081/k,哈哈,一切如愿!KarriGell 正如小白想象的有了实时的反应,不 像 CherryPy 每次修改,都要重启服务。 这是因为在 KarriGell 中,就像 PHP 开发那样,每次修改好后,刷一下页面,你的修改 立即就能显示在页面输出可以和服务运行对比,如图 KDay1-5 所示。 PCS302“Leo”分享了什么 是文学化编辑环境,以及利 用文学化视角来组织软件工 程时的最佳体验;这里的 @nosent 是指 Leo 中结点 的特殊名称,可以引起不同 系统响应的名称。 PCS302 KDays“Web 应用故事” | 91 图 KDay1-5 页面输出和服务运行对比 小技巧 [Directories] ... allow_directory_listing = all 在实际开发过程中,经常要查看实际发布目录中有什么内容,打开配置文件中的上述配 置项,Karrigell 将自动发布没有默认首页的目录内容索引,其默认的目录内容索引样式 如图 KDay1-6 所示。 图 KDay1-6 默认的目录内容索引样式 92 | 实例故事 小结 简单回想一下今天任务的完成情况: 1. 评估 KarriGell 的可用性——已经达成! 2. 简单地确定 KarriGell 的应用开发/组织方式——已经达成! 那么,接下来确定明日计划。 1. 对于“简单问卷”本身,先要完成的是怎么在 KarriGell 中读入外部文件,并可以在 页面中修改,然后提交保存…… 2. 利用大侠 Limodou 自创的 Dict4Ini 模块来理解问卷的设计,并将问答收集为一系列 文件,最终仍由 Python 统计成绩…… 实例下载 实例下载时请使用 SVN 下载地址。 实例下载: http://openbookproject.googlecode.com/svn/trunk/LovelyPython/KDays/kday1 精巧地址:http://bit.ly/4qP7S8 我们来回顾一下本日成果: kday1/ |-- q/ 问卷设计文本收藏目录 | `-- easy051201.cfg 第一份问卷设计 `-- try.pih 第一个动态页面 练习 经过这一天的学习, 熟悉 Karrigell.ini 基本设置和基本环境部署后(可参考 README.txt 中),创建一个自己的站点(mysite),并依次实现以下功能: 将 Karrigell 站配置发布到 localhost:8081 端口; 在首页(index.pih)中输出“Hello,Karrigell's world!”即在浏览器的地址栏中输入 localhost:8081/mysite 页面显示出“Hello,Karrigell's world!” 添加一个要求有密码控制的登录页(login.pih),即当输 localhost:8081/mysite 后,首 先进行登录,我们输入姓名和密码后,转入首页,其上显示“Hello,姓名's world!” 具体可参见相关截图。 PCS208 “dict4ini”分享了 专门用以解析和输出 .ini 类 M$ Windows 配置文件 文本的支持模块的基本使用 思路;这是 UliPad 作者 Limodou 自个儿组织分享出 来的。 PCS208 KDays“Web 应用故事” | 93 KDay2 通过表单直接完成功能 不管三七二十一直接完成心目中的功能! 小白秉承之前的习惯,先计划当日期望达到的目标: 什么可以直接读取原先的问卷设计.ini 文本,并显示到页面中的文本框 ()中; 什么可以提交包含问卷设计文本框的表单,并将内容保存到服务器上的指定目录中, 完成问卷的更新。 规划 行者 Rockety 对 KarriGell 的设置有很好的介绍。小白参考了相关的介绍后重新规划了站 点的开发,并对/path/to/Karrigell/conf/Karrigell. ini 相关部分进行修订。 ... [Alias] .. q=%(base)s/KDays 追加这一行设置行,这样一来,就可以通过 http://localhost:8081/q/kday2/来访问今天的开 发成果了!但是没有在 obp 目录中安置 index.htm/index.pih 之类的默认首页,KarriGell 是不会让小白看到什么的! 约定以下全局性变量: qpath = "q/" pubq = qpath+"easy051201.cfg" Rockety 的 Karrigell 使用 体验,访问地址: http://wiki.woodpecker.org .cn/moin/RocketyKarrigell 精巧地址: http://bit.ly/3wEwn3 另外在 PCS301 中也对其 最常用部分的配置进行了说 明。 94 | 实例故事 使用这样的全局变量定义下来,使用 Leo 快速地将所有的文件控制起来,这样计划就进 一步明确了: 在页面 http://localhost:8081/q/kday2/mana.pih 看到并可以编辑 ini 问卷设计文稿,点 击提交后转到; 在页面 http://localhost:8081/q/kday2/qpage.pih 可以看到真正保存下来的问卷设计内 容。 Cheetah 小白很早之前从身边的师兄师姐那儿就模糊地听说过 MVC 模式,说什么将数据模式/前端 表现/业务控制等分层实现;但是在这个简单得连数据库都没有的实例小项目中,顶多是 VC 分层了,在技术列表咨询了一下,有行者推荐说模板系统中 Cheetah 非常地好和稳定, 那就先尝试它了! 稍稍看一下子示例,就可了解到 Cheetah 的基本使用是这样的: 1 from Cheetah.Template import Template # 0. 引入模板 2 page = open("你的模板文件.tmpl","r").read() # 1. 加载模板 3 vPool = {'cfgtxt':"随便什么字串的值就成"} # 2. 加载数据 4 print Template(page, searchList=[vPool]) # 3. 渲染输出 模板文件中有 $cfgtxt 的地方就会替换为实际数据; 模板文件就是标准的 HTML 文件,不同之处在于各个期望有动态数据出现的地方, 变成了 $开头的变量名; $开头的变量名 只要在合适的时机,先于 print Template() 完成赋值就好。 以如下模板文件片段为例:

$cfgtxt

Leo 组织实现 小白已经习惯了通过 Leo 在一个统一界面中把握程序的全部逻辑层次/章节/元素。 可以使用中文作章节名,通过问卷来组织设计文案,就是@nosent easy051201.cfg。当 PCS304 “Python Web 应 用框架纵论”综合列举了流 行的一些 Web 应用框架系 统,同时也分享了一些实用 的模板选择和使用体验。有 关 Cheetch 模板系统的简要 介绍在: http://wiki.woodpecker.org .cn/moin/CheetahTemplate Org 精巧地址: http://bit.ly/31kKBK。 PCS304 KDays“Web 应用故事” | 95 然要使用@path q 来配合,这样你对 easy051201.cfg 的修改可以立即输出为具体的文件。 Ctrl+Alt+c 和 Ctrl+Alt+v 是 Leo 中的复制和粘贴操作快捷键。快速从原先的 @nosent index.pih 复制整个节点为: @nosent mana.pih 管理页面入口 @nosent questionnaire.tmpl 修改问卷模板,Cheetah 的。 VC 分层 至此,小白组织好了自个儿的 VC 实现:@nosent mana.pih 虽然几乎是纯 HTML 页面, 但是通过<%Include("qdesign.py")%> 来包含一个纯操作脚本,我们把它看做数据控制 (Control)层,@nosent questionnaire.tmpl 模板,我们把它看做表现(View)层。 哦,原来所谓分层实现,就是将自然的做法起个 NB 的名称。 八股文样 模式化的处理脚本 从文学化编程角度看,Web 应用的前端应用脚本,应该说都一个样儿!图 KDay2-1 是 使用 Leo 规格化的功能页面设计情景。 图 KDay2-1 使用 Leo 规格化的功能页面设计情景 即: 96 | 实例故事 1. 脚本说明 @...@c 部分; 2. 脚本声明 << page declarations >> 引用的下层部分; 3. 行为定义 @others 包含的所有下级节点; 4. 实际尝试 <> 引用的下层部分。 编辑实现 编辑实现其实就是将指定的文件内容读出来发布到页面的输入框()中。 我 们在模板中先做准备: 然后处理脚本: 1 #简化引用对象名 2 from Cheetah.Template import Template as ctTpl 3 vPool = {} 4 vPool['cfgtxt'] = open(pubq,"r").read() 5 page = open("questionnaire.tmpl","r").read() 6 txp = ctTpl(page, searchList=[vPool]) 7 print txp 完成!如图 KDay2-2 所示。 图 KDay2-2 问卷设计主页面 最后运行! PCS302“Leo”分享了什么 是文学化编程环境,以及利 用文学化视角来组织软件工 程时的最佳体验。 PCS302 KDays“Web 应用故事” | 97 展示实现 展示实现,指的是将 ini 的内容整理为 HTML 页面展示。 同样快速地组织一下: 1. @nosent qpage.pih 访问的页面 http://localhost:8081/q/kday2/qpage.pih; 2. @nosent qpage.py 实际的数据重组。 关键代码 From dict4ini import DictIni:这里使用的是 Limodou 提供的 dict4ini.py(字典 化 ini 操作模块); 创建 qpage.py→def expage(dict):问卷输出函式,来将 ini 内容整理为相应的页面。 1 exp +="
    " 2 # 将字串的字典键值依照数字方式排序 3 k = [int(i) for i in dict.ask.keys()] 4 k.sort() # 没有回传的数组重整处理 5 for i in k: 6 ask = dict.ask[str(i)] 7 exp +="
  • %s"%ask["question"] 8 exp +="
      " 9 qk = [j for j in ask.keys()] 10 qk.sort() 11 for q in qk: 12 if 1==len(q): 13 exp +="
    • %s"%ask[q] 14 else: 15 pass 16 exp +="

      正确答案::%s

      "%ask["key"] 17 exp +="
    " 18 exp +="
  • " 19 exp +="
" 20 return exp 上述代码中的双层循环就可以对应地将所有类似[ask/1] 的问题,以及其中的所有 类似“a = 赞!”的选择项按照列表的形式输出,如图 KDay2-3 所示! 98 | 实例故事 图 KDay2-3 问卷设计文本解析输出的页面效果 串联页面 串联页面,即将编辑页面和展现页面串联起来。只要使用 HTML 表单元素(
), 利用内置的提交声明 ACTION 就可以了,例如: 在模板中补充以上声明。 测试为先!我们在 qpage.py 中加入 print QUERY,以确认表单到底传送过来了什么,如 图 KDay2-4 所示。 PCS208 “dict4ini”知名行 者 Limodou 从 UliPad 项 目中贡献出来的对象化操作 ini 配置文本支持模块;在 PCS208 中 分 享 了基本技 巧。 PCS208 KDays“Web 应用故事” | 99 图 KDay2-4 使用 Karrigell 内置对象 QUERY 观察表单提交 OK!一切吻合想象,的确是个字典对象的传送,键值就是