一、Python文件读写操作
Python对文件的处理,对文件的读写是我们日常中最用的操作了,不管你是分析日志还是要将生成的结果写到文件里,都需要用到读文件的读写操作。读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
- 读操作
在Python下对文件操作非常容易,我们使用它的内建函数open()就可以打开一个文件,如果你要打开个文件一般的用法是:
1 |
f = open(filename, mode, [buffer]) |
open方法可以接收三个参数:文件路径、打开模式和缓冲区大小。Python 3未缓冲的文本I/O被禁用,貌似有个Bug:issue17404。
mode | 说明 | 注意 |
r | 只读方式打开文件 | 只能打开已存在的文件 |
w | 只写方式打开文件 | 文件不存在会自动创建,文件存在则清空 |
a | 追加方式打开 | 文件不存在会自动创建 |
a+ | 追加和读写方式打开 | |
rb、wb、ab | 二进制方式打开 |
在模式后面使用’+’表示同时支持写入、输出操作,如r+(如果以r+这种方式写入那么文件必须提前存在)、w+、a+。在模式后面附加’b’表示以二进制方式打开,如rb+、wb+。这些模式基本覆盖了我们日常的对文件操作,当然除了这些还有一些其他模式,想深入了解的可以去自己学习下,如果是对运维来说这些基本够了。
如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容(硬伤就是它会一次将读入文件的所有内容,所以读大文件,会把你内存吃满),Python把内容读到内存,用一个str对象表示:
1 |
>>> f.read() |
最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:
1 |
>>> f.close() |
由于文件读写时都有可能产生IOError,一旦出错,后面的close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try … finally来实现:
1 2 3 4 5 6 |
try: f = open('/tmp/1.txt', 'r') print(f.read()) finally: if f: f.close() |
但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:这和前面的try … finally是一样的,但是代码更佳简洁,并且不必调用close()方法。
1 2 |
with open('/tmp/1.txt', 'r') as f: print(f.read()) |
调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。
也可以采用文件迭代的方式访问文件,这种方式应该是最常用的读取文件方式了,因为我们可以直接迭代文件对象,例子如下:
1 2 3 |
f = open('/tmp/1.txt','r') for line in f: print(line) |
另外,可以使用readline()方法,这个函数在某些场景有估计会用到,它的作用就是每次从文件中读取一行出来,我们看个例子:
1 2 3 4 |
>>> f = open('/tmp/1.txt','r') >>> f.readline() '111111\n' >>> f.close() |
这个是每次读取一行,所以需要用到while 循环,用if来判断文件是否已经结束,如果结束就跳出,否则打印改行。
1 2 3 4 5 6 7 |
f = open('/tmp/1.txt','r') while True: line = f.readline() if not line: break else: print(line.strip()) |
readlines()方法:这个方法我用的不多,因为它也有个硬伤就是它返回一个列表,所以当文件足够大的时候,返回的列表就会异常的大,就会非常慢,不过它的好处就是可以快速释放文件资源,我们了解下就可以,对付一般文件还是可以的,例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> f = open('/tmp/1.txt','r') >>> res = f.readlines() >>> f.close() >>> type(res) <type 'list'> >>> for line in res: ... print(line.strip()) ... 111111 222222 333333 444444 |
- file-like object
像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
- 二进制
前面讲的默认都是读取文本文件,并且是ASCII编码的文本文件。要读取二进制文件,比如图片、视频等等,用’rb’模式打开文件即可:
1 2 3 |
>>> f = open('/tmp/test.jpg', 'rb') >>> f.read() '\xff\xd8\xff\xe1\x00\x18Exif\x00\x00... #十六进制表示的字节; |
- 字符编码
字符编码要读取非ASCII编码的文本文件,就必须以二进制模式打开,再解码。比如GBK编码的文件(VIM编辑器下通过set fileencoding=gbk可以创建一个gbk编码文件):
1 2 3 4 |
>>> f = open('/tmp/gbk.txt', 'rb') >>> u = f.read().decode('gbk') >>> print(u) 中国 |
如果每次都这么手动转换编码嫌麻烦(写程序怕麻烦是好事,不怕麻烦就会写出又长又难懂又没法维护的代码),Python还提供了一个codecs模块帮我们在读文件时自动转换编码,直接读出unicode:
1 2 3 4 5 |
>>> import codecs >>> with codecs.open('/tmp/gbk.txt', 'r', 'gbk') as f: ... print(f.read()) ... 中国 |
Python 3开始,要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,使用上更加方便了。例如,读取GBK编码的文件:
1 2 3 |
>>> f = open('/tmp/gbk.txt', 'r', encoding='gbk') >>> f.read() '中国' |
遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
1 |
>>> f = open('/tmp/gbk.txt', 'r', encoding='gbk', errors='ignore') |
- 写文件
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:
1 2 3 |
>>> f = open('/tmp/test.txt', 'w') >>> f.write('Hello, world!\n') >>> f.close() |
你可以反复调用write()来写入文件,但是务必要调用close()来关闭文件。
当调用write(str)时,Python解释器调用系统调用想把把内容写到磁盘,但是Linux内核有文件缓存机制,所以缓存到内核的缓存区,当调用close()或flush()时才会真正的把内容写到文件。忘记调用close()或flush()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:
1 2 |
with open('/tmp/test.txt', 'w') as f: f.write('Hello, world!\n') |
二、操作文件常用方法和属性
- 方法
next():一行一行地读取数据。
1 2 3 4 5 |
>>> f = open('/tmp/1.txt','r') >>> f.next() '111111\n' >>> f.next() '222222\n' |
close():关闭文件同时会刷新内存数据到磁盘上。
flush():刷新缓冲区将内存中的数据保存到磁盘上。
readline():一次返回文件中的一行。
readlines():返回文件中的所有行并生成列表,指针就会转向最后一个字节。
tell():返回当前指针在文件中的位置单位字节。
read(size):一次读多少个字节。
write(‘new line.’):往文件中写入数据可以加\n换行,文件权限要为W。
seek([偏移量],[当前位置]):移动指针的偏移位置单位字节。当前位置:0表示开头位置、1表示中间位置、2表示结尾位置。
1 2 3 4 5 6 7 |
>>> f = open('/tmp/1.txt','r+') >>> f.seek(2,0) >>> print(f.read()) 1111 222222 333333 444444 |
writelines():往文件中写字符串序列
1 2 3 4 5 |
>>> import os >>> f = open('/tmp/test.txt','w+') >>> f1 = os.listdir('/etc') >>> f.writelines(f1) >>> f.flush() |
- 属性
closed:查看当前文件是否关闭。
name:查看文件名。
mode:查看当前文件打开方式。
1 2 3 4 5 6 7 |
>>> f = open('/tmp/test.txt','r') >>> f.closed False >>> f.name '/tmp/test.txt' >>> f.mode 'r' |
完结。。。