在任何一门高级语言中,文件I/O操作是非常重要的一个模块,python中的文件操作和C语言中非常类似,也很简单,虽然很简单,但是文件操作中也有很多需要主要的地方,尤其是读写效率的问题。
文件的打开
和c语言一样python使用open函数打开一个文件,该函数定义如下:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True)
当然对于我们而言通常不需要关心那么多参数,通常我们只关心最前面的两个参数,file和mode,file表示的是要打开的文件,通常是一个包含绝对路径的字符串,第二个参数mode表示打开的访问方式,默认值为r,表示只读。第二个参数的常用的取值和对应含义如下:
r 只读模式,也是默认的打开模式,如果文件不存在将抛出异常
w只写模式,不能读取文件内容,如果文件存在将清空文件内容,如果不存在则创建
a追加模式,可读,文件存在则在文件末尾追加内容,不存在则创建
+更新模式,可读可写,通常与r,w,a连用,打开一个磁盘文件用于更新(进行读写操作)
r+读写模式,即r与+模式的组合,如果文件不存在将抛出异常
w+读写模式,与r+不同的是该模式会清空文件已存在的内容,且如果文件不存在会创建该文件
如果你想学习Python可以来这个群,首先是四七二,中间是三零九,最后是二六一,里面有大量的学习资料可以下载。
可以看到这和c语言中是一模一样的,通常我们在实际项目中使用的较多的模式是‘r+’,’w+’或者’a+’。open函数的使用很简单,和C语言一样:
filePath='C:/Users/htq/Desktop/testFile.txt'
但是正如前面说到的,以r或者r+模式打开问的时候如果文件不存在程序会抛出异常No such file or directory,因此上面的写法显然是不对的,我们必须对这种可能出现的情况进行处理,因此通常我们会使用try/except语句,像下面这样,个人觉得java语言在这方面设计的很好,对于可能会出现IO异常的操作,java会要求必须try/catch否则IDE会自动提示错误。而python如果不处理不会提示错误,但运行时可能出错。
try:
在上面这段代码中我们对可能出现的异常进行捕获,然后处理(该代码中是使用print打印出出错原因),即使出现异常程序仍可以顺序往下执行,如果不这么做那么当打开文件失败(如文件不存在)时程序会终止退出,因此文件操作时必须要对可能出现的异常进行捕获处理。
那么上述代码是否就是合理的代码呢?我们知道,当我们打开一个文件进行操作后,最好是在不需要读写的时候使用close()函数关闭该操作。因此一个规范的打开文件的操作应该是这样的:
try:
注意在finally语句块中,调用close()关闭读写操作之前,要对该文件对象进行判空操作,因为在open函数打开文件的时候可能失败,即出现异常,此时fileObject为空,但finally块不管是否出现异常是一定会执行的,因此需要先判空再操作,这基本上才是打开一个文件的规范代码,但是每次这么写太麻烦,因此python为我们提供了with as语句。
try:
使用with as语句在该语句块结束的时候文件会自动关闭,即使出现异常也会close()
file is closed after the with statement’s suite is finished—even if an exception occurs:
但是注意with as 语句打开文件,只是帮我们自动处理了close()的调用而已,异常的捕获还是需要我们自己处理,因此对文件进行打开操作时使用with as且捕获异常是一个好的习惯。
文件的读写
文件的读
文件的读主要涉及到三个重要的函数read(),readline()和readlines()
read(size):如果不指定size的大小,此时size默认为-1,事实上此时read()会调用readall()函数,此时会一次性读取文件的全部内容,因此如果文件比较大,会占用非常大的内存,程序将会运行的十分缓慢,效率较低,此时通常不会直接使用read()而是会指定一个size参数,每次读取size个字节的内容。
readline():默认读取一行,包括行结束符,因此如果需要得到不包含行结束符的数据的话需要程序员自己处理。
readlines():该函数会读取文件所有的行然后把结果作为一个字符串列表返回
通过这三个函数的描述我们知道,如果读取的文件不是很大的话,我们通常使用read()或者readLines()函数一次性读取文件的全部内容,而不是使用readLine()一次读取一行,因此这样效率较慢,如果文件比较大,我们可以使用readLine()或者read(size)的方式一次读取一行或者size个字节进行处理,否则会很占内存。另外这三个函数不会处理行结束符,因此如果我们希望得到的数据不包含行结束符(windows下为’\n’,mac下为’\r’)的时候,需要我们手动处理,以readLines()为例,代码如下:
data=[line.strip() for line in fileObject.readlines()]
其中strip()如果不指定参数的话,默认去除首尾空白符包括(‘\n’, ‘\r’, ‘\t’, ‘ ‘)
文件的写
文件的写和读是一一对应的关系,但是不存在writeline()函数,只存在write()和writelines(),含义和文件的读是一一对立的,因此也很容易理解。
write(size):向文件中写入size个字节
writelines():向文件中一次写入一个字符串列表
需要注意的是write()和writelines()不会写入行结束符,因此我们向文件中写入内容是需要自己处理行结束符。
文件指针的移动
在对文件操作时一个常用的场景就是先读取文件的全部内容到一个字符串列表中,然后对该字符串列表中的数据进行处理后写回原文件,把之前文件的内容替换掉,这种模式只能使用’r+’模式打开,那么这就存在一个问题就是之前因为读取了文件的内容,因此此时文件指针已经指向了文件的末尾,因此如果我们直接调用write()或者writelines()的话,此时的效果事实上相当于追加模式,会写到原文件的末尾,因此此时我们需要移动文件指针到文件开始位置,然后写入数据。文件指针的移动使用seek()函数。
seek(offset[,whence])
该函数的第一个参数表示相对某个位置的偏移量,这里所说的相对某个位置就是第二个参数的含义,第二个参数可以取0,1,2三个值,0表示从文件开头开始算起,1表示从当前位置开始算起,2表示从文件末尾算起。默认值为0,从文件开始位置算起。因此代码如下:
fileObject.seek(0)#让文件指针移动到文件开始位置,然后写入处理后的数据
代码实例
使用场景:在很多场合下我们需要读取一个文件中的内容,然后对该内容中的某些字符串进行特定处理,如替换某些关键词,然后将处理后的结果写回到原文件。本实例展示的是从一个记录了baidu,alibaba,tencent,jd,netease,360等知名互联网企业的文本文件中找出带有alibaba的行,然后替换为tencent.最后将结果写回原文件。
filePath='C:/Users/htq/Desktop/testFile.txt'
这个实例是项目中进行文件操作是经常会遇到的,而且也把前面所总结的知识点全部用到了,包括前面说的对文件操作使用with as,使用try/except对可能出现的异常进行处理等一些好的习惯。
总结
对文件操作尽量使用with as,确保文件操作会调用close()
文件操作一定要使用try/except对可能出现的异常进行处理
如果操作的文件不大的话使用readlines()一次性读取全部文件效率较高,此时不要使用readline(),如果文件较大使用read(size)或者readline()进行处理,减少对内存的消耗
在向原操作文件写回处理的内容的时候注意文件指针的位置,某些场景下需要调用seek(0)回到文件开始位置再写入。