9.3 文件内建方法
open()成功执行并返回一个文件对象之后,所有对该文件的后续操作都将通过这个“句柄”进行。文件方法可以分为四类:输入、输出、文件内移动及杂项操作。所有文件对象的总结被列在了表9.3中。我们现在来讨论每个类的方法。
9.3.1 输入
read()方法用来直接读取字节到字符串中,最多读取给定数目个字节。如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾。未来的某个版本可能会删除此方法。
readline()方法读取打开文件的一行(读取下个行结束符之前的所有字节)。然后整行,包括行结束符,作为字符串返回。和read()相同,它也有一个可选的size参数,默认为-1,代表读至行结束符。如果提供了该参数,那么在超过size个字节后会返回不完整的行。
readlines()方法并不像其他两个输入方法一样返回一个字符串。它会读取所有(剩余的)行然后把它们作为一个字符串列表返回。它的可选参数sizhint代表返回的最大字节大小。如果它大于0,那么返回的所有行应该大约有sizhint字节(可能稍微大于这个数字,因为需要凑齐缓冲区大小)。
Python 2.1中加入了一个新的对象类型用来高效地迭代文件的行:xreadlines对象(可以在 xreadlines模块中找到)。调用file.xreadlines()等价于xreadlines.xreadlines(file)。xreadlines()不是一次性读取所有的行,而是每次读取一块,所以用在for循环时可以减少对内存的占用。不过,随着Python 2.3中迭代器和文件迭代的引入,没有必要再使用xreadlines()方法,因为它和使用iter(file)的效果是一样的,或者在for循环中,使用for eachLine in file代替它。它来得容易,去得也快。
另一个废弃的方法是readinto(),它读取给定数目的字节到一个可写的缓冲器对象,和废弃的buffer()内建函数返回的对象是同个类型(由于buffer()已经不再支持,所以readinto()被废弃)。
9.3.2 输出
write()内建方法的功能与read()和readline()相反。它把含有文本数据或二进制数据块的字符串写入到文件中去。
和readlines()一样,writelines()方法是针对列表的操作,它接受一个字符串列表作为参数,将它们写入文件。行结束符并不会被自动加入,所以如果需要的话,你必须在调用writelines()前给每行结尾加上行结束符。
注意这里并没有“writeline()”方法,因为它等价于使用以行结束符结尾的单行字符串调用write()方法。
核心笔记:保留行分隔符
当使用输入方法如read()或者readlines()从文件中读取行时,Python并不会删除行结束符,这个操作被留给了程序员。例如这样的代码在Python程序中很常见:
类似地,输出方法write()或writelines()也不会自动加入行结束符。你应该在向文件写入数据前自己完成。
9.3.3 文件内移动
seek()方法(类似C中的fseek()函数)可以在文件中移动文件指针到不同的位置。offset字节代表相对于某个位置偏移量。位置的默认值为0,代表从文件开头算起(即绝对偏移量),1代表从当前位置算起, 2代表从文件末尾算起。如果你是一个C程序员,并且使用过了fseek(),那么,0、1、2分别对应着常量 SEEK_SET、SEEK_CUR和SEEK_END。当人们打开文件进行读写操作的时候就会接触到seek()方法。
text()方法是对seek()的补充;它告诉你当前文件指针在文件中的位置——从文件起始算起,单位为字节。
9.3.4 文件迭代
一行一行访问文件很简单:
:
在这个循环里,eachLine代表文本文件的一行(包括末尾的行结束符),你可以使用它做任何想做的事情。
在Python 2.2之前,从文件中读取行的最好办法是使用file.readlines()来读取所有数据,这样程序员可以尽快释放文件资源。如果不需要这样做,那么程序员可以调用file.readline()一次读取一行。曾有一段很短的时间,file.xreadlines()是读取文件最高效的方法。
在Python 2.2中,我们引进了迭代器和文件迭代,这使得一切变得完全不同,文件对象成为了它们自己的迭代器,这意味着用户不必调用read*()方法就可以在for循环中迭代文件的每一行。另外我们也可以使用迭代器的next方法,file.next()可以用来读取文件的下一行。和其他迭代器一样,Python也会在所有行迭代完成后引发Stoplteration异常。
所以请记住,如果你见到这样的代码,这是“完成事情的老方法”,你可以安全地删除对readline()的调用。
:
文件迭代更为高效,而且写(和读)这样的Python代码更容易。如果你是Python新手,那么请使用这些新特性,不必担心它们过去是如何。
9.3.5 其他
close()通过关闭文件来结束对它的访问。Python垃圾收集机制也会在文件对象的引用计数降至零的时候自动关闭文件。这在文件只有一个引用时发生,例如fp=open(…),然后fp在原文件显式地关闭前被赋了另一个文件对象。良好的编程习惯要求在重新赋另一个文件对象前关闭这个文件。如果你不显式地关闭文件,那么你可能丢失输出缓冲区的数据。
fileno()方法返回打开文件的描述符。这是一个整型,可以用在如os模块(os.read())的一些底层操作上。
调用flush()方法会直接把内部缓冲区中的数据立刻写入文件,而不是被动地等待输出缓冲区被写入。isatty()是一个布尔内建函数,当文件是一个类tty设备时返回True,否则返回False。truncate()方法将文件截取到当前文件指针位置或者到给定size,以字节为单位。
9.3.6 文件方法杂项
我们现在重新实现第2章中的第一个文件例子:
我们曾经介绍过这个程序。与大多数标准的文件访问方法相比,它的不同在于它读完所有的行才开始向屏幕输出数据。很明显如果文件很大,这个方法并不好。这时最好还是回到最可靠的方法:使用文件迭代器,每次只读取和显示一行:
核心笔记:行分隔符和其他文件系统的差异
操作系统间的差异之一是它们所支持的行分隔符不同。在POSIX(Unix系列或MacOS X)系统上,行分隔符是换行符NEWLINE ( \n)字符。在旧的MacOS下是RETURN( \r),而DOS和Wind32系统下结合使用了两者 (\r\n)。检查一下你所使用的操作系统用什么行分隔符。
另一个不同是路径分隔符(POSIX使用“/”; DOS和Windows使用“\”;旧版本的MacOS使用“:”);它用来分隔文件路径名;标记当前目录和父目录。当我们创建要跨这3个平台的应用的时候;这些差异会让我们感觉非常麻烦(而且支持的平台越多越麻烦)。幸运的是Python的os模块设计者已经帮我们想到了这些问题。os模块有5个很有用的属性。它们被列在了表9.2中。
不管你使用的是什么平台,只要你导入了os模块,这些变量自动会被设置为正确的值,减少了你的麻烦。
还要提醒大家的是:print语句默认在输出内容末尾后加一个换行符,而在语句后加一个逗号就可以避免这个行为。readline()和readlines()函数不对行里的空白字符做任何处理(参见本章练习),所以你有必要加上逗号。如果你省略逗号,那么显示出的文本每行后会有两个换行符,其中一个是输入是附带的,另一个是print语句自动添加的。
文件对象还有一个truncate()方法,它接受一个可选的size作为参数。如果给定,那么文件将被截取到最多size字节处。如果没有传递size参数,那么默认将截取到文件的当前位置。例如:你刚打开了一个文件,然后立即调用truncate()方法,那么你的文件(内容)实际上被删除,这时候你其实是从0字节开始截取的(tell()将会返回这个数值)。
在学习下一小节之前,我们再来看两个例子,第一个展示了如何输出到文件,第二个展示了文件的输出和输入,以及用于文件定位的seek()和tell()方法的使用。
这里我们每次从用户接收一行输入,然后将文本保存到文件中。由于raw_input()不会保留用户输入的换行符,调用write()方法时必须加上换行符。而且,在键盘上很难输入一个EOF (end-of-file)字符,所以,程序使用句点(.)作为文件结束的标志,当用户输入句点后会自动结束输入并关闭文件。
第二个例子以可读可写模式创建一个新的文件(可能是清空了一个现有的文件).在向文件写入数据后,我们使用seek()方法在文件内部移动,使用tell()方法展示我们的移动过程。
表9.3文件对象的内建方法列表。
文件对象除了方法之外,还有一些数据属性。这些属性保存了文件对象相关的附加数据,例如文件名(file.name),文件的打开模式(file.mode),文件是否已被关闭(file.closed),以及一个标志变量,它可以决定使用print语句打印下一行前是否要加入一个空白字符(file.softspace)。表9.4列出了这些属性并做了简短说明。
一般说来,只要你的程序一执行,你就可以访问3个标准文件。它们分别是标准输入(一般是键盘)、标准输出(到显示器的缓冲输出)和标准错误(到屏幕的非缓冲输出)(这里所说的“缓冲”和“非缓冲”是指open()函数的第3个参数)。这些文件沿用的是C语言中的命名,分别为stdin, stdout和stderr。我们说“只要你的程序一执行就可以访问这3个标准文件”,意思是这些文件已经被预先打开了,只要知道它们的文件句柄就可以随时访问这些文件。
Python中可以通过sys模块来访问这些文件的句柄。导入sys模块以后,就可以使用sys.stdin、sys.stdout和sys.stderr访问。print语句通常是输出到sys.stdout;而内建raw_input()则通常从sys.stdin接收输入。
记得sys.是文件,所以你必须自己处理好换行符。而print语句会自动在要输出的字符串后加上换行符。
sys模块通过sys.argv属性提供了对命令行参数的访问。命令行参数是调用某个程序时除程序名以外的其他参数。这样命名是有历史原因的,在一个基于文本的环境里(比如UNIX操作系统的shell环境或者DOS-shell),这些参数和程序的文件名一同被输入的。但在IDE或者GUI环境中可能就不会是这样了,大多IDE环境都提供一个用来输入“命令行参数”的窗口;这些参数最后会像命令行上执行那样被传递给程序。
熟悉C语言的读者可能会问了,“argc哪去了?”argc和argv分别代表参数个数(argument count)和参数向量(argument vector)。argv变量代表一个从命令行上输入的各个参数组成的字符串数组;argc变量代表输入的参数个数。在Python中,argc其实就是sys.argv列表的长度,而该列表的第一项sys.argv[0]永远是程序的名称。
总结如下:
sys.argv是命令行参数的列表;
len (sys.argv)是命令行参数的个数(也就是argc)。
我们来创建这个名为argv.py的测试程序:
下面是该脚本程运行的输出:
命令行参数有用吗? Unix操作系统中的命令通常会接受输入,执行一些功能,然后把结果作为流输出出来。这些输出的结果还可能被作为下一个程序的输入数据,在完成了一些其他处理后,再把新的输出送到下一个程序。如此延伸下去。各个程序的输出一般是不保存的,这样可以节省大量的磁盘空间,各个程序的输出通常使用“管道”实现到下个程序输入的转换。
这是通过向命令行提供数据或是通过标准输入实现的。当一个程序显示或是发送它的输出到标准输出文件时,内容就会出现在屏幕上——除非该程序被管道连接到下一个程序,那么此时程序的标准输出就成为下个程序的标准输入。你现在明白了吧?
命令行参数使程序员可以在启动一个程序的时候对程序行为做出选择。在大多情况下,这些执行操作都不需要人为干预,通过批处理执行。命令行参数配合程序选项可以实现这样的处理功能。让计算机在夜里有空闲时完成一些需要大量处理的工作。
Python还提供了两个模块用来辅助处理命令行参数。其中一个(最原始的)是getopt模块,它更简单些,但是不是很精细。而Python 2.3引入的optparse模块提供了一个更强大的工具,而且它更面向对象。如果你只是用到一些简单的选项,我们推荐getopt,但如果你需要提供复杂的选项,那么请参阅 optparse。
对文件系统的访问大多通过Python的os模块实现。该模块是Python访问操作系统功能的主要接口。os模块实际上只是真正加载的模块的前端,而真正的那个“模块”明显要依赖与具体的操作系统。这个“真正”的模块可能是以下几种之一:posix (适用于Unix操作系统),nt (Win32), mac (旧版本的MacOS), dos (DOS), os2 (OS/2),等等。你不需要直接导入这些模块。只要导入os模块,Python会为你选择正确的模块,你不需要考虑底层的工作。根据你系统支持的特性,你可能无法访问到一些在其他系统上可用的属性。
除了对进程和进程运行环境进行管理外,os模块还负责处理大部分的文件系统操作,应用程序开发人员可能要经常用到这些。这些功能包括删除/重命名文件,遍历目录树,以及管理文件访问权限等。表9.5列出os模块提供的一些常见文件或目录操作函数。
另一个模块os.path可以完成一些针对路径名的操作。它提供的函数可以完成管理和操作文件路径名中的各个部分,获取文件或子目录信息,文件路径查询等操作。表9.6列出了 os.path中的几个比较常用的函数。
这两个模块提供了与平台和操作系统无关的统一的文件系统访问方法。例9.1 (ospathex.py)展示了os和os.path模块中部分函数的使用。
例9.1 os和 os.path模块例子(ospathex.py)
这段代码练习使用一些os和os.path模块中的功能。它创建一个文本文件,写入少量数据,然后重命名,输出文件内容。同时还进行了一些辅助性的文件操作,比如遍历目录树和文件路径名处理。
os的子模块os.path更多用于文件路径名处理。比较常用的属性列于表9.6中。在Unix平台下执行该程序,我们会得到如下输出:
在 DOS窗口下执行这个例子我们会得到非常相似的输出:
这里就不逐行解释这个例子了,我们把这个留给读者做练习。下面我们来看看一个类似的交互式例子(包括错误),我们会把代码分成几个小段,然后依次进行讲解。
代码的第一部分导入了 os模块(同时也包含os.path模块)。然后检查并确认 ‘/tmp’是一个合法的目录,并切换到这个临时目录开始我们的工作。之后我们用getcwd()方法确认我们当前位置。
接下来,我们在临时目录里创建了一个子目录,然后用listdir()方法确认目录为空(因为我们刚创建它)。第一次调用listdir()调用时出现的问题是因为我们没有传递要列目录的路径名。我们马上在第二次调用时修正了这个失误。
这里我们创建了一个有两行内容的test文件,之后列目录确认文件被成功创建。
这一段代码使用了 os.path的一些功能,包括我们之前看到过的 join()、isfile()、isdir()、split()、basename()和splitext(),我们还调用了 os下的rename()函数。接下来,我们显示文件的内容,之后,删除之前创建的文件和目录。
核心模块:os (和 os.path)
从上面这些长篇讨论可以看出,os和os.path模块提供了访问计算机文件系统的不同方法。我们在本章学习的只是文件访问方面,事实上 os模块可以完成更多工作。我们可以通过它管理进程环境,甚至可以让一个Python程序直接与另外一个执行中的程序“对话”。你很快就会发现自己离不开这个模块了。更多关于os模块的内容请参阅第14章。
无论你只是想简单地运行一个操作系统命令,调用一个二进制可执行文件,或者其他类型的脚本(可能是shell脚本,Perl,或是Tcl/Tk),都需要涉及运行系统其他位置的其他文件。尽管不经常出现,但是有时甚至会需要启动另外一个Python解释器。我们将把这部分内容留到第14章去讨论。如果读者有兴趣了解如何启动其他程序,以及如何与它们进行通讯,或者是Python执行环境的一般信息,都可以在第14章找到答案。
在本书的很多练习里,都需要用户输入数据。这可能需要用户多次输入重复的数据,尤其是如果你要输入大批数据供以后使用时,你肯定会厌烦。这就是永久储存大显身手的地方了,它可以把用户的数据归档保存起来供以后使用,这样你就可以避免每次输入同样的信息。在简单的磁盘文件已经不能满足你的需要,而使用完整的关系数据库管理系统(relational database management systems,RDBMS)又有些大材小用时,简单的永久性储存就可以发挥它的作用。大部分永久性储存模块是用来储存字符串数据的,但是也有方法来归档Python对象。
9.9.1 pickle和 marshal模块
Python提供了许多可以实现最小化永久性储存的模块。其中的一组(marshal和pickle)可以用来转换并储存Python对象。该过程将比基本类型复杂的对象转换为一个二进制数据集合,这样就可以把数据集合保存起来或通过网络发送,然后再重新把数据集合恢复原来的对象格式。这个过程也被称为数据的扁平化、数据的序列化或者数据的顺序化。另外一些模块(dbhash/bsddb,dbm,gdbm,dumbdbm等)及它们的“管理器” (anydbm)只提供了 Python字符串的永久性储存。而最后一个模块(shelve)则两种功能都具备。
我们已经提到marshal和pickle模块都可以对Python对象进行储存转换。这些模块本身并没有提供“永久性储存”的功能,因为它们没有为对象提供名称空间,也没有提供对永久性储存对象的并发写入访问(concuirent write access)o它们只能储存转换Python对象,为保存和传输提供方便。数据储存是有次序的(对象的储存和传输是一个接一个进行的)。marshal和pickle模块的区别在于marshal只能处理简单的Python对象(数字、序列、映射以及代码对象),而pickle还可以处理递归对象,被不同地方多次引用的对象,以及用户定义的类和实例。pickle模块还有一个增强的版本叫cPickle,使用C实现了相关的功能。
9.9.2 DBM风格的模块
db系列的模块使用传统的DBM格式写入数据,Python提供了 DBM的多种实现:dbhash/bsddb、dbm、gdbm和dumbdbm等。你可以随便按照你的爱好使用,如果你不确定的话,那么最好使用anydbm模块,它会自动检测系统上已安装的DBM兼容模块,并选择“最好”的一个。 dumbdbm模块是功能最少的一个,在没有其他模块可用时,anydbm才会选择。这些模块为用户的对象提供了一个命名空间,这些对象同时具备字典对象和文件对象的特点。不过不足之处在于它们只能储存字符串,不能对Python对象进行序列化。
9.9.3 shelve模块
最后,我们来看一个更为完整的解决方案,shelve模块。shelve模块使用anydbm模块寻找合适的DBM模块,然后使用cPickle来完成对储存转换过程。shelve模块允许对数据库文件进行并发的读访问,但不允许共享读/写访问。这也许是我们在Python标准库里找到的最接近于永久性储存的东西了。可能有一些第三方模块实现了“真正”的永久性储存。图9-1展示了储存转换模块与永久性储存模块之间的关系,以及为何shelve对象能成为两者的最好的选择的。
图 9-1 用于序列化和永久性储存的 Python模块
核心模块: pickle和 cPickle
你可以使用pickle模块把Python对象直接保存到文件里,而不需要把它们转化为字符串,也不用底层的文件访问操作把它们写入到一个二进制文件里。pickle模块会创建一个Python语言专用的二进制格式,你不需要考虑任何文件细节,它会帮你干净利索地完成读写对象操作,唯一需要的只是一个合法的文件句柄。
pickle模块中的两个主要函数是dump()和load()。dump()函数接受一个文件句柄和一个数据对象作为参数,把数据对象以特定格式保存到给定文件里。当我们使用load()函数从文件中取出已保存的对象时,pickle知道如何恢复这些对象到它们本来的格式。我们建议你看一看pickle和更“聪明”的shelve模块,后者提供了字典式的文件对象访问功能,进一步减少了程序员的工作。
cPickle是pickle的一个更快的C语言编译版本。
还有大量的其他模块与文件和输入/输出有关,它们中的大多数都可以在主流平台上工作。表9.7列出了一些文件相关的模块。
fileinput模块遍历一组输入文件,每次读取它们内容的一行,类似Perl语言中的不带参数的“<>”操作符。如果没有明确给定文件名,则默认从命令行读取文件名。
glob和fnmatch模块提供了老式Unix shell样式文件名的模式匹配,例如使用星号()通配符代表任意字符串,用问号(?)匹配任意单个字符。
核心提示:使用 os.path.expanduser()的波浪号(~)进行扩展
虽然glob和fnmatch提供了 Unix样式的模式匹配,但它们没有提供对波浪号(用户目录)字符,〜的支持。你可以使用os.path.expanduser()函数来完成这个功能,传递一个带波浪号的目录,然后它会返回对应的绝对路径。这里是两个例子,分别运行在Unix和Win32环境下:
另外Unix衍生系统还支持 “~user”这样的用法,表示指定用户的目录,还要注意Win32版本函数没有使用反斜杠来分隔目录路径。
gzip和zlib模块提供了对zlib压缩库直接访问的接口。gzip模块是在zlib模块上编写的,不但实现了标准的文件访问,还提供了自动的gzip压缩/解压缩bz2类似于gzip,用于操作bzip压缩的文件。
程序员可以通过1.6中新增的zipfile模块创建,修改和读取zip归档文件。(tarfile文件实现了针对tar归档文件的相同功能)。在2.3版本中,Python加入了导入归档zip文件中模块的功能。更多细节请参阅12.5.7小节。
shutil模块提供高级的文件访问功能,包括复制文件、复制文件的访问权限、递归地目录树复制等。
tempfile模块用于生成临时文件(名)。
在关于字符串一章中,我们介绍了 StringlO模块(和它的C语言版本cStringIO),并且介绍了它是如何在字符串对象顶层加入文件操作接口的。这个接口包括文件对象的所有标准方法。
我们在前面永久性储存一节(9.9节)中介绍的模块还有文件和字典对象混合样式的例子。
其他的Python类文件对象还有网络和文件socket对象(socket模块),用于管道连接的popen*()文件对象(os和popen2模块),用于底层文件访问的fdopen()文件对象(os模块),通过URL (UniformResource Locator,统一资源定位符)建立的到指定Web服务器的网络连接(urllib模块)等。需要注意的是并非所有的标准文件方法都能在这些对象上实现,同样的,这些对象也提供了一些普通文件没有的功能。
具体内容请参考这些模块的相关文档,你可以在下边这些地址中找到关于file()/open(),文件,文件对象的更多信息,(这里我们还建议读者参考Python标准库,译者注)
http://docs.python.org/lib/built-in-flincs.html
http://docs.python.org/lib/bltin-file-objects.html