应用 Python 解决一些实际问题

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

韦建明 (wei@uni-duisburg.de), 德国Duisburg-Essen大学

2004 年 1 月 10 日

Python 是一种简洁优美的脚本语言,它的诸多优点使它在完成某些任务时轻松自如。本文通过几个具体的例子阐明了这一点。

关于Python

Python 是一种简洁优美的编程语言,它具有面向对象的特征,较好的粘合其他语言的能力及跨平台性。然而我认为同样重要的是, 它简单易学,书写代码简洁快速。此外,Python 提供了较多的模快,包含了相当多的功能,所以只要有一个可行的想法,那么用 Python 解决起来会是比较容易的。下面几个例子都源于我遇到的一些实际问题。借助于 Python,这些问题的解决都显得轻而易举。





回页首


自动删除某些文件

某些软件在工作时会自动生成一些备份文件。比如我用 Vim 做文本编辑,用 Autocad 绘图时,这些程序都会自动生成一些备份的文件。随着文件数量的增长,每隔一段时间就要清理一下。当然可以选择手工清理,不过考虑到这些文件是分散在不同的目录下,而且数量比较多,所以手工清理还是有些麻烦。于我写了一个简单的 Python 脚本来自动完成这一任务。下面这段代码扫描D盘下的所有目录,并删除目录下的有关备份文件:

	from os.path import walk, join, normpath
	from os import chdir, remove
	def scan(arg, dirname, names)
		for file in names:
1			if file[-1:]=="~" or file[-4:]==".bak":
2				files = normpath(join(dirname,file))
3				chdir(dirname)
4				print "deleting", files
5				remove(file)
6				print "done!"
	if __name__== "__main__":	
			path = chdir('d:\\\\')
7	walk(path, scan, 0)

对以上代码的简单分析和解释:

  • 基本的想法是利用脚本对各个目录进行扫描,对目录下每一个文件进行判断(1句),如果是某个程序生成的备份文件就删除掉(5句)。
  • 备份文件的扩展名都有一定的特征,比如 Vim 备份文件的最后一个字符是波浪号~,而 Autocad 的备份则以 bak 结束。这些特征是判断一个文件是否应该被删除的依据。
  • 7 句 walk(path, scan, 0) 是一个 Python 的内置函数。用来遍历目录 path。显而易见借助于 Python 提供的这个函数,扫描目录的工作比较轻松地完成了,从而使编程的难度降低许多。
  • 最后要说明的一点是,删除某个文件时要知道它的绝对路径而且要在那个文件的所在目录下进行,否则 Python 会提示找不到要处理的文件。2 句得到了文件的绝对路径,3 句 chdir(dirname) 则把当前的目录变成要删除文件的所在目录。




回页首


提取嵌入在文档中的图像

这个问题的提出也是来自实际的需要。 我在阅读与课题有关的各种格式的电子文档时,有时发现包含在文档里面的图像很有用,因为这些图像可以在论文或报告中直接引用, 所以最好能把它们保存下来。 在 Linux 下工作时我用过 Pdfimages,那是一个从 PDF 文档中提取图像的工具。但我希望能找到一个工具可以同时快速处理不同格式的文档,而且能在 Windows 下工作。感 谢 Python,它使我能用比较简单的代码去实现这样一个工具。

基本的想法是先读取要处理的文档,然后在其中找到图像部分。因为各种图像在起始部分都包含了自己的标识。比如JPEG图像的标识是JFIF,PNG的是PNG。找到这些标识后读取包含这个图像的相应字节,再把它以二进制格式保存下来.根据这个想法,代码可以分为两部分:寻找图像和保存图像。下面的这两段代码用来从一个PowerPoint文档NORFA_COD.ppt中提取图像,其中第一部分的代码如下:

import sys
import os
import string
1		headers=[("JFIF", 6, "jpg"), ("GIF", 0, "gif"), ("PNG", 1, "png")]
2		marker=[]
3		filename = "d:\\\\article\\\\ppt\\\\NORFA_COD.ppt"
	try: 
4		fid = open(filename, 'rb')
	except:
5		sys.exit(1)
6		numlin = len(fid.readlines())
7		fid .seek(0)
8	i = 0; s = 0
9	curlin = fid.readline()
	while i < numlin:
		for flag, offset, ext in headers:
10		index = string.find(curlin, flag)
		if index < 0:
11			continue
		else:
12			pos = s + index -offset
13			marker.append((pos, ext))
14	s = s + len(curlin)
15	curlin = fid.readline()
16	i += 1

以上代码首先把要处理的文件以二进制格式逐行读入(9句),然后在其中寻找有没有包含1句headers里的图像标识。如果没有找到,就读取下一行(11句)。如果发现的话,就把图像的起始位置和标识记录在一个字典marker里(12-13句)。在扫描完整个文档后,可以得到一个包含图像信息的字典marker。

有了marker里的信息,就可以进行图像的保存工作了。这部分的代码是:

17	fid.seek(0)
18	j = len(marker)
19	imgnum = 0
	if j == 0:
20		print "No images included in the document"
21		sys.exit(1)
	for i in range(0, j):
22		if i == j-1:
23			info  = marker[i]
24			thispos = info[0]
25			thisext = info[1]
26			nextpos = s
27			gap = nextpos - thispos
28			fid.seek(thispos)
29			data = fid.read(gap)
30			imgname = "imgname%02d.%s" % (i, thisext)
31			fid1 = open(imgname, 'wb')
32			fid1.write(data)
33			fid1.close()
34			imgnum += 1
		else:
35			info = marker[i]
36			thispos = info[0]
37			thisext = info[1]
38			nextinfo = marker[i+1]
39			nextpos = nextinfo[0]
40			gap = nextpos - thispos
41			fid.seek(thispos)
42			data = fid.read(gap)
43			imgname = "imgname%02d.%s" % (i, thisext)
44			fid1 = open(imgname, 'wb')
45			fid1.write(data)
46			fid1.close()
47			imgnum += 1
48	fid.close()
49	print "%02d imgaes have benn extracted" % imgnum 

下面来说明一下如何存储图像。首先注意到虽然定位一个图像的起始位置比较容易,但要确定它的结束位置却比较困难。我用了一个间接的方法来解决这个问题,那就是先读取当前图像的起始位置,至于它的结束位置则取为下一个图像(如果有的话)的起点位置(35-41句); 如果当前的图像是文档中最后的一个,那么结束位置则取为文档的结束位置(23-28句)。这样做的好处是,由于存储的范围是从当前图像的起始位置到下一幅图像的起始位置,这就保证了当前图像可以被完整的保存下来,不用担心它被中途截断。缺点是保存的数据中不仅包括了当前的图像,还有一些其他的内容。也就是说图像中包含了一些冗余的数据,这样就造成了文件的尺寸比图像实际的尺寸要大。当然这不会影响它的浏览,因为图形软件在读取这些文件时会把冗余的部分给截断掉,不过如果想要在自己的论文或报告中引用这些图像的话,最好还是要给它们"廋身"一下。方法也很简单,只要用图形软件把这些图像重新保存一遍就可以恢复原来的尺寸了。

此外,从上面的代码不难看出,要处理更多种类的图像,只要把有关图像的格式信息添加到headers就可以了. 而被处理的文档可以是任意的格式.





回页首


调用Matlab绘制保存数据

最近我做实验时需要把得到的数据用Matlab绘图并保存下来。一个小问题是原始的数据文件中不仅有数据还有一些注释的部分(注释部分在数据之前,每行以#开头)。直接用Matlab绘图比较麻烦,因为不能直接处理数据,所以或者要把数据单独提取出来,另存成一个文件。或者就要用Matlab写一个过滤文本的程序。不过Matlab处理文本并不是它的强项。 所以这两个办法我都不很满意。Python再次给我提供了解决的方案。一方面Python具有很强的文本处理能力,另一方面Python对Matlab这种交互式的程序提供了直接调用的的途径,所以写一个Python脚本来完成这一任务无疑是比较合适的。下面是实现的代码:

import os
import string
1	filepath = "d:\\\\exp\\\\chgeff_lar_1"
2	filename="chgeff_lar_1"
3	fid = open(filepath, 'r')
4	lines = fid.readlines()
5	fid.close()
6	x = []; y = []
7	for line in lines:
8		if line[0]=="#" or len(line)==1:
9			continue
10		else:
11			xval, yval = string.split(line)
12			x.append(float(xval))
13			y.append(float(yval))
14	fid = open(filename+'.m', 'w')
15	fid.write("""
16		x = %s
17		y = %s
18		plot(x, y)
19		xlabel('Particle diameter (nm)')
20		ylabel('Charging efficiency')
21		print -deps  %s.eps
22		pause(10)
23	"""  %  (x, y, filename))
24	fid.write("exit")
25	fid.close()
26	cmd = "d:\\\\matlab6p5\\\\bin\\\\win32\\\\matlab.exe -nodesktop -r  " + filename
27	os.system(cmd)

以上6-13句实现两个功能,一是过滤文本(8-9句),通过检查每一行的第一个字符及行的长度,把注释行和空行给去掉。二是把读到的每一行数据自动分配到两个变量x,y中(10-13句)。然后Python把一组Matlab代码(16-24句)写到文件filename.m中。最后利用os模块的system函数调用Matlab绘图及保存(26-27句)。从这个例子也可以看出,Python和其他语言协同工作的能力是比较强的。





回页首


一些感受

Python是一种优美高效的脚本语言。它简单易学,代码的书写也很简洁明了。在完成某些任务时,使用Python可以使你专心于你的问题,而不需要把很多时间花在编程上。借助于Python的众多模块,编程的难度降低了许多, 时间也减少了。我想对于一个普通的用户(比如我)而言,学习一门语言的最终目的是用它来解决实际的问题,提高工作效率。从这个角度说,Python绝对是一个值得考虑的选择。





回页首


说明

以上程序在Python2.3 IDLE环境下运行通过,操作系统是德文版Windows ME。



参考资料

  • 关于Pdfimages的详细介绍可以去 http://www.fifi.org。那里有几乎所有Linux命令的介绍。

  • 文中使用Python的函数的详细说明可以参见Python附带的帮助文档或者Python的网站 http://www.python.org/doc/


关于作者

韦建明,目前在德国Duisburg-Essen大学学习,Ph.D. Candidate. 电子邮件: wei@uni-duisburg.de. 联系电话:0049-0179-7060212