20.python的文件处理
我们日常在处理文件的时候一般都遵循这样的逻辑:打开文件,操作文件,保存关闭文件。
但在python中,又分为以下几步:创建文件对象,对文件对象进行操作(读入,写入之类的),关闭文件。
由于文件操作在python2.x和python3.x中区别还是比较大的,3.x可以接受更多的参数。
所以在此说明:以下内容都是针对python2.x而言的,准确来说是python2.7。
下面来逐一分析:
1.创建文件对象
创建文件对象的方法有两种,第一张是使用工厂函数 file(name[, mode[, buffering]]) -> file object ,另一种是调用内置函数 open(name[, mode[, buffering]]) -> file object 。
两者其实并不存在本质上的区别,实际上 open 也是调用 file 实现的,但是python官方建议我们使用 open 进行文件对象的创建,所以我们也顺应官方的建议,下面的演示都是基于 open 来进行。
首先,当我们使用 open 创新一个文件对象时需要一些参数,其中 name 是必须的,它接受一个字符串,表示文件名,文件名可以采用绝对路径,也可以采用相对路径,而 mode 表示模式,它接受一个字符串表示要用什么模式来创建这个文件对象,而模式的不同也会对文件造成影响,下面是一个关于模式的总结表:
文件模式 | 操作 |
r | 以只读方式打开,默认 |
rU 或 Ua | 以读方式打开, 同时提供通用换行符支持 (PEP 278) |
w | 以写方式打开 (必要时清空) |
a | 以追加模式打开 (从 EOF 开始, 必要时创建新文件) |
r+ | 以读写模式打开 |
w+ | 以读写模式打开 (参见 w ) |
a+ | 以读写模式打开 (参见 a ) |
rb | 以二进制读模式打开 |
wb | 以二进制写模式打开 (参见 w ) |
ab | 以二进制追加模式打开 (参见 a ) |
rb+ | 以二进制读写模式打开 (参见 r+ ) |
wb+ | 以二进制读写模式打开 (参见 w+ ) |
ab+ | 以二进制读写模式打开 (参见 a+ ) |
下面是关于这些模式的进一步解释:
r : 顾名思义,同该模式创建的文件对象中的进行读取,而不能进行写入等操作,当要打开的文件不存在时抛出 IOError 异常。
w:以只写的方式打开,该模式下,只能进行写入操作,而不能进行读取操作,如果打开的文件存在,则清空原文件再打开,若打开的文件不存在则创建该文件后再打开。这种模式相对危险,因为无论文件存不存存在,最后操作的都是一个空文件,此时写入会从头开始写,也就是文件指针在文件的开头处。
a : 以追加的模式打开,该模式下不能进行文件的读取,但能进行写入操作。如果打开的文件存在,则打开此文件,将文件的指针移到文件的末尾,此时任何新写入的内容都会在文件的最末尾处。如果文件不存在,则新建一个文件,从头开始写。和 w 不同的是当打开的文件存在时不会清空原文件,只是进行追加写入。
r+ :r 本身不能进行写入操作,而扩展成 r+ 之后,就能够进行写入了。r+ 打开的文件并不会清空文件,写入时其指针会在最开头,也就是我文件中的内容是 123456,我用 r+ 写入了 'abc' 字符串时,文件内容就变成了 abc456 了。同样的,文件不存在的时候还是会抛出异常。
w+,a+:因为 w 和 a 都不具有读权限,所以在进行 + 号拓展后,都能进行读操作了,其他的行为了原来的一样。
U :通用换行符支持,不同平台用来表示行结束的符号是不同的, 例如 \n, \r, 或者 \r\n 。但是如果只写了一种处理换行符的方法,在其他平台就无法同样了,如果要为每一个平台都写一个方法就太麻烦了。所以python在 Python 2.3 引入了 UNS。当你使用 'U' 标志打开文件的时候, 所有的行分割符(或行结束符, 无论它原来是什么)通过 Python 的输入方法(例如:read() )返回时都会被替换为换行符 NEWLINE(\n)。('rU' 模式也支持 'rb' 选项) 。 这个特性还支持包含不同类型行结束符的文件,文件对象的 newlines 属性会记录它曾“看到的”文件的行结束符。
b:二进制模式,在 linux 中默认都是用二进制打开的,所以这个选项对 linux 而言可有可无,但如果真的需要使用二进制模式,还是建议写上,增加跨平台能力。使用此模式进行写入操作时,不仅可以写入字符串,还可以写入 buffer 对象。
buffering 表示缓用于指示访问文件所采用的缓冲方式。 其中 0 表示不缓冲, 1 表示只缓冲一行数据, 任何其它大于 1 的值代表使用给定值作为缓冲区大小。不提供该参数或者给定负值代表使用系统默认缓冲机制,既对任何类电报机( tty )设备使用行缓冲, 其它设备使用正常缓冲。一般情况下使用系统默认方式即可。
最后要总结一点,创建了文件对象,并不意味着读取了文件内容,这与我们日常生活中的打开文件的定义是不同的。Python的打开文件是获取了文件的句柄,也就是文件的操作入口,而读取文件内容还需要将其读入到python的内存中,也就是所谓的输入。
2.输入
所谓的输入,就是将文件的内容读入的python中,有下面几种方法。
1. read([size]) -> read at most size bytes, returned as a string.
如果 size(单位为字节) 是负数或者没有给,就一直读到EOF,也就是文件结束。返回一个保括所有内容的字符串(包括换行符)。注意,当在非阻塞模式下,数据就算低于要求也可能会返回,即使没有尺寸参数。
这个方法是一次性读入,也就是加载1gb的文件就会直接占用1gb的内存,所以不适合读取大的文件。
f = open('test.txt') a = f.read() print a print repr(a)
文件内容:
代码输出:
注意这里的换行符。文件中使用了3个回车,也就有3个换行符。
我们在python的编码中使用过repr()函数来获取一个字符串的编码,这里我们使用这个函数,看看打开的文件都是什么编码。
首先,我的编码声明为: # coding= utf-8
编码声明为: # coding= gbk
发现无论编码声明是什么,返回的字符串都是一样的。说明编码声明并不影响从文件读取到的字符串的编码。
那么,我们用 utf-8 编码写一个文件:
#! /usr/bin/env python # coding= utf-8 f = open('test1.txt','w') f.write('第一行\n第二行\n第三行\n') f.close()
再进行一下读取:
和上面的一样。
再用gbk写一个文件:
#! /usr/bin/env python # coding= gbk f = open('test2.txt','w') f.write('第一行\n第二行\n第三行\n') f.close()
再读取一下:
结果不同了。
说明一个问题:在文件对象中读取获得的字符串,其编码和python的编码声明无关,只与文件本身保存时使用的编码有关。
python的编码声明只影响在python创建的字符串,所以我根据这个特性,在python创建相应编码的字符串,再保存到文件中,所有文件内字符的编码才会不一样,希望大家不要绕晕了。
讲到这里,就可以看python中是如何进行文件的编码转换的了。
编码的转换其实是针对于字符串的,所有用到的也是字符串中的内置方法:
S.decode([encoding[,errors]]) -> object
该方法返回解码后的字符串。
encoding -- 要使用的编码,如"UTF-8"(默认)。errors -- 设置不同错误的处理方案。默认为 'strict',意为编码错误引起一个UnicodeError。 其他可能得值有 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace' 以及通过 codecs.register_error() 注册的任何值。
代码示例:
f = open('test2.txt','r') a = f.read() b = a.decode(encoding='gbk') print b print repr(b)
可以看出,进行解码以后得到的是Unicode字符串。
接下来我们看看如何编码。
S.encode([encoding[,errors]]) -> object
该方法返回编码后的字符串。
encoding -- 要使用的编码,如"UTF-8"(默认)。errors -- 设置不同错误的处理方案。默认为 'strict',意为编码错误引起一个UnicodeError。 其他可能得值有 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace' 以及通过 codecs.register_error() 注册的任何值。
代码示例:
f = open('test2.txt','r') a = f.read() b = a.decode(encoding='gbk') c = b.encode(encoding='utf-8') print c print repr(c)
得到的就是 utf-8 编码的字符串了,也就是完成了gbk-->Unicode-->utf-8的转换了。此时可以看看我在python的编码中的内容,就明白了Unicode作为桥梁的含义了。
f = open('test2.txt','r') a = f.read() f.close() b = a.decode(encoding='gbk') c = b.encode(encoding='utf-8') f = open('test2.txt','w') f.write(c) f.close()
使用以上的代码就可以完成文件的编码转换了,当然还有一定的优化空间,这里仅作为逻辑演示。
接下来继续进行输入方法的说明。
2. readline([size]) -> next line from the file, as a string.
一次读取一行,保留换行符,返回一个字符串对象。size(默认为 -1, 代表读至行结束符)为非负数时,用来限制最大读取的字节数,当字节数设置少于真正一行的字节时,会返回不完整的一行,超过时不影响。当为EOF也就是文件结束时,返回空字符串。配合for循环使用可以进行逐行遍历。由于返回的也是字符串,所以也可以进行对返回对象使用字符串的方法,例如编解码等。
f = open('test2.txt', 'r') for x in range(2): print f.readline()
由于print关键字会默认添加换行符,而文件中已经又换行符了,所以会出现两个换行符,也就出现了输出这样的隔行显示。可以加入一个逗号取消print这种默认行为。
f = open('test2.txt', 'r') for x in range(2): print f.readline(),
另外,还有注意一个问题,不用想下面这里循环:
f = open('test2.txt', 'r') for x in f.readline(): #相当于读取一行,对每个字节进行遍历 print x #此时打印的就是一个字节,utf-8中,显示中文需要3个字节,输出的就是乱码了 print repr(x) #看一下是否是字节
每次读取一行,内存消耗自然就低了点,但是很难知道文件有多少行,所以也不是一个理想的遍历方式。
3. readlines([size]) -> list of strings, each a line from the file.
方法并不像其它两个输入方法一样返回一个字符串, 它会读取所有(剩余的)行,然后把它们作为一个字符串列表返回。 它的可选参数 size 代表返回的大字节大小。如果它大于 0 , 那么返回的所有行应该大约有 size 字节(可能稍微大于这个数字, 因为需要凑齐缓冲区大小。比如说缓冲区的大小只能为 4K 的倍数,如果 size 为 15k,则后返回的可能是 16k)。
因为返回的是一个包含所有行的列表,所以可以直接对其进行遍历,注意和上面的区别。
f = open('test2.txt', 'r') for x in f.readlines(): print x,
但是,它也是一次性读取了整个文件,所以内存占用也是非常大的。
此时,有一个更高效的 xreadlines 方法,参数也是一样的,只不过其本质是一个生成器,也就是每次调用都返回一行,迭代的时候是逐行读取,效率更高。
但是,这并不是最好的方法,最好的方法是直接迭代文件对象:
f = open('test2.txt', 'r') for x in f: print x,
这是自python2.2中引入的迭代器和文件迭代后的新特性,文件对象成为了它们自己的迭代器,这意味着用户不必调用 read*() 方法就可以在 for 循环中迭代文件的每一行。 另外我们也可以使用迭代器的 next 方法, file.next() 可以用来读取文件的下一行。和其它迭代器一样, Python 也会在所有行迭代完成后引发 StopIteration 异常。但 for 循环会自动调用 next 方法和处理迭代完成后引发的异常,所以直接迭代文件对象成为了最佳的用法。
另个废弃的方法是 readinto() ,它读取给定数目的字节到一个可写的缓冲器对象,和废弃的 buffer() 内建函数返回的对象是同个类型。(由于 buffer() 已经不再支持,所以 readinto() 被废弃。)
3.输出
1. write(str) -> None. Write string str to file.
向文件中写入字符串,这里不再演示了。
2. writelines(sequence_of_strings) -> None. Write the strings to the file.
接受一个字符串列表作为参数,将它们写入文件。 行结束符并不会被自动加入,所以如果需要的话,你必须在调用 writelines()前给每行结尾加上行结束符。
注意这里并没有 "writeline()" 方法, 因为它等价于使用以行结束符结尾的单行字符串调用 write() 方法.
4.文件内指针的移动
文件内的指针相对与我们平常所见的光标,它表示我们各种操作的位置。例如我们用 r+ 打开文件并写入东西时,光标就在文件的开头,所以出现开头举例的会覆盖原有字符的现象,而 a 模式打开后光标在文件的最后,所以在 a 模式下写入内容会在文件的末尾追加。
1. seek(offset[, whence]) -> None. Move to new file position.
在文件中移动光标的位置 从 whence ( 0 代表文件起始; 1 代表当前位置,当前位置由打开模式决定,或者之前有移动过光标; 2 代表文件末尾)偏移 off 字节 ,off为正时向右移动,为负时向左移动。当光标在文件开头时,只能为正;在文件末尾时,只能为负。虽然部分文件支持光标超出末尾,但还是不超过的好。
2. tell() -> current file position, an integer (may be a long integer).
返回一个整数(有可能是长整数),表示当前光标所在的位置。
3. truncate([size]) -> None. Truncate the file to at most size bytes.
截取文件到大 size 字节, 默认为当前文件位置(size=file.tell())。所谓的截取就是光标前面的保留,后面的全部去掉。
5.关闭保存
1. close() -> None or (perhaps) an integer. Close the file.
关闭文件。如果文件不关闭,则对文件的各种操作都保存在缓冲区中,关闭文件才能把缓冲区里的内容写入磁盘中。
2. flush() -> None. Flush the internal I/O buffer.
在不关闭文件的前提下,将缓冲区里的内容保存到磁盘中。
6.其他方法
1. isatty() -> true or false.
判断 file 是否是一个类 tty 设备
2. x.next() -> the next value, or raise StopIteration
返回文件的下一行(类似于 file.readline() ), 在没有其它行时引发 StopIteration 异常。
7.file对象相关属性
file.closed True 表示文件已经被关闭, 否则为 False
file.encoding 文件所使用的编码 - 当 Unicode 字符串被写入数据时, 它们将自动使 用 file.encoding 转换为字节字符串; 若 file.encoding 为 None 时使用系统默认编码
file.mode 文件打开时使用的访问模式
file.name 文件名
file.newlines 未读取到行分隔符时为 None , 只有一种行分隔符时为一个字符串, 当文件有多种类型的行结束符时,则为一个包含所有当前所遇到的行结束符的列表。
file.softspace 为 0 表示在输出一数据后,要加上一个空格符,1 表示不加。这个属性一般程序员用不着,由程序内部使用。
关于文件操作就先总结到这,有什么错误或补充的以后会进行修正。