文件输入/输出
数据持久化最简单的类型是普通文件,有时也叫平面文件(flat file)。它仅仅是在一个文件 名下的字节流,把数据从一个文件读入内存,然后从内存写入文件。Python 很容易实现这 些文件操作,它模仿熟悉的和流行的 Unix 系统的操作。
读写一个文件之前需要打开它:
fileobj = open(filename,mode)
下面时对该open()调用的简单解释:
- fileobj时open()返回的文件对象。
- filename是该文件的字符串名
- mode是指明文件类型和操作的字符串
mode 的第一个字母表示对其的操作。
- r 表示读模式
- w 表示写模式,如果文件不存在则新创建,如果存在则重写新内容
- x 表示文件不存在的情况下新创建并写入文件
- a 表示如果文件存在,在文件末尾追加写内容
mode 的第二个字母是文件类型1:
- t (或者省略)代表文本类型
- b 代表二进制文件
打开文件之后就可以调用函数来读写数据。
最后需要关闭文件。
接下来我们会在一个程序中用Python字符串创建一个文件,然后返回。
使用write()写文本文件
首先创建我们使用的文本源:
In [4]: poem = '''There was a young lady named Bright,
...: Whose speed was far faster than light;
...: She started one day
...: In a relative way,
...: And returned on the previous night.'''
In [5]: len(poem)
Out[5]: 154
将文本源添加到文件'relativity' 中:
In [6]: fout = open('relativity','wt')
In [7]: fout.write(poem)
In [8]: fout.close()
函数write()返回写入文件的字节数。和print()一样,它没有增加空格或换行符。同样,我们也可以在一个文本文件中使用print()。
In [15]: fout = open
In [16]: fout = open('relativity','wt')
In [17]: print(poem,file=fout)
In [18]: fout.close()
这就产生了一个问题:到底是使用 write() 还是 print() ? print() 默认会在每个参数后 面添加空格,在每行结束处添加换行。 在之前的例子中,它在文件 relativity 中默认添 加了一个换行。为了使 print() 与 write() 有同样的输出,传入下面两个参数。
- sep分隔符:默认是一个空格 ' '
- end结束字符:默认是一个换行符'\n'
除非自定义参数,否则print()会使用默认参数。在这里,我们通过空字符串替换print()添加对的所有多余输出:
In [19]: fout = open('relativity','wt',sep=' ',end=' ')
In [20]: print(poem,file=fout)
In [21]: fout.close()
上面的场景,打开文件就清晰很多,在未设置换行时候,最后一行是没有换行的。
如果源字符串非常大,可以将数据分块,直到所有字符被写入:
In [19]: fout = open('relativity','wt')
In [20]: size = len(poem)
In [21]: offset = 0
In [22]: chunk = 100
In [23]: while True:
...: if offset > size:
...: break
...: fout.write(poem[offset:offset+chunk])
...: offset += chunk
...:
In [24]: fout.close()
第一次写入100个字符,然后写入剩下的50个字符。
如果文件 'relativity' 已经存在,使用模式 x 可以避免重写文件:
In [25]: fout = open('relativity','xt')
Traceback (most recent call last): File "<stdin>", line 1, in <module> FileExistsError: [Errno 17] File exists: 'relativity'
可以添加一个异常处理:
In [27]: try:
...: fout = open('relativity', 'xt')
...: fout.write('stomp stomp stomp')
...: except:
...: print('relativity already exists!. That was a close one.')
...:
relativity already exists!. That was a close one.
使用read(),readline()或者readlines()读文本文件
我们可以使用不带参数的read()函数一次读入文件的所有内容。
In [17]: fin = open('relativity','rt')
In [18]: poem = fin.read()
In [19]: fin.close()
同样也可以设置最大的读入字符数限制 read() 函数一次返回的大小。下面一次读入 100 个 字符,然后把每一块拼接成原来的字符串 poem:
In [40]: fin = open('relativity','rt')
In [41]: while True:
...: fragment = fin.read(chunk)
...: if not fragment:
...: break
...: poem += fragment
...:
In [42]: fin.close()
读到文件结尾之后,再次调用 read() 会返回空字符串(''),if not fragment 条件被判为 False。此时会跳出 while True 的循环。 当然,我们也能使用 readline() 每次读入文件的一 行。在下面栗子中,通过追加每一行拼接成原来的字符串 poem:
In [5]: poem = ''
In [6]: fin = open('relativity','rt')
In [7]: while True:
...: line = fin.readline()
...: if not line:
...: break
...: poem += line
...:
In [8]: fin.close()
对于一个文本文件,即使空行也有1字符长度(换行字符 '\n'),自然就会返回True。当 文件读取结束后,readline()(类似 read())同样会返回空字符串,也被 while True 判 为 False。
In [9]: poem = ''
In [10]: fin = open('relativity','rt')
In [11]: for line in fin:
...: poem += line
...:
In [12]: fin.close()
前面所有的栗子最终都返回单个字符串poem。函数readlines()调用时每次读取一行,并且返回单行字符串的列表:
In [19]: fin = open('relativity','rt')
In [20]: lines = fin.readlines()
In [21]: print(len(lines),'lines read')
5 lines read
In [22]: for line in lines:
...: print(line,end='')
...:
使用write()写二进制文件
如果文件模式字符串中包含'b',那么文件会以二进制模式打开。这种情况下,读写的是字节而不是字符串。
我们首先生成一串二进制数据:
In [25]: bdata = bytes(range(0,256))
In [26]: len(bdata)
Out[26]: 256
以二进制模式打开,并且一次写入所有的数据:
In [27]: fout = open('bfile','wb')
In [28]: fout.write(bdata)
Out[28]: 256
write()返回写入的字节数
In [29]: fout.close()
对于文本文件,也可以分块写二进制数据。
In [30]: fout = open('bfile','wb')
In [31]: size = len(bdata)
In [32]: chunk = 100
In [33]: offset = 0
In [34]: while True:
...: if offset > size:
...: break
...: fout.write(bdata[offset:offset+chunk])
...: offset += chunk
...:
In [35]: fout.close()
使用read()读取二进制文件
下面的栗子只需要用'rb'打开文件即可:
In [38]: fin = open('bfile','rb')
In [39]: bdata = fin.read()
In [40]: len(bdata)
Out[40]: 256
In [41]: fin.close()
使用with自动关闭文件
如果你忘记关闭已经打开的一个文件, 在该文件对象不再被引用之后 Python 会关掉此文 件。这也就意味着在一个函数中打开文件,没有及时关闭它,但是在函数结束时会被关 掉。然而你可能会在一直运行中的函数或者程序的主要部分打开一个文件,应该强制剩下 的所有写操作完成后再关闭文件。
Python的上下文管理器(context manager)会清理一些资源,例如打开的文件。它的形式为 with expression as variable:
In [42]: with open('withfile','wt') as fout:
...: fout.write(poem)
...:
完成上下文管理器的代码后,文件会自动关闭
使用seek()改变位置
无论是读或者写文件,Python都会跟踪文件中的位置。函数tell()返回距离文件开始处的字节偏移量。函数seek()允许跳转到其他字节偏移量的位置。这意味着可以不用从头读取文件的每一个字节,直接跳转到最后位置并只读一个字节也是可以的。
下面就是我们的栗子:
使用之前的二进制文件bfile
In [43]: fin = open('bfile','rb')
In [44]: fin.tell()
Out[44]: 0
使用 seek() 读取文件结束前最后一个字节:
In [45]: fin.seek(255)
Out[45]: 255
一直读到文件结束:
In [46]: bdata = fin.read()
In [47]: len(bdata)
Out[47]: 1
In [48]: bdata[0]
Out[48]: 255
调用seek()函数的时候,可以使用两个参数:seek(offset,origin)。
- 如果origin等于(默认为0),从开头便宜offset个字节
- 如果origin等于1,从当前位置偏移offset个字节
- 如果origin等于2,距离最后结束处偏移offset个字节
这些值也在标准os模块中被定义:
In [1]: import os
In [2]: os.SEEK_SET
Out[2]: 0
In [3]: os.SEEK_CUR
Out[3]: 1
In [4]: os.SEEK_END
Out[4]: 2
我们可以用不同的方法读取最后一个字节:
In [9]: fin = open('bfile','rb')
结尾前的一个字节:
In [10]: fin.seek(-1,2)
Out[10]: 255
In [11]: fin.tell()
Out[11]: 255
在调用seek()函数时不需要额外调用tell()。上面只是说明两个函数具有相同的偏移量
一直读到结尾:
In [12]: bdata = fin.read()
In [13]: len(bdata)
Out[13]: 1
In [14]: bdata[0]
Out[14]: 255
下面时从文件的当前位置寻找的栗子:
In [15]: fin = open('bfile','rb')
下面的栗子返回最后两个字节:
In [16]: fin.seek(254,0)
Out[16]: 254
In [17]: fin.tell()
Out[17]: 254
在此基础上前进一个字节:
In [18]: fin.seek(1,1)
Out[18]: 255
In [19]: fin.tell()
Out[19]: 255
最后一直读到文件结尾:
In [20]: bdata = fin.read()
In [21]: len(bdata)
Out[21]: 1
In [22]: bdata[0]
Out[22]: 255
这些函数对于二进制文件都是极其重要的。当文件是 ASCII 编码(每个字符一个字节) 时,也可以使用它们,但是计算偏移量会是一件麻烦事。其实,这些都取决于文本的编码 格式,最流行的编码格式(例如 UTF-8)每个字符的字节数都不尽相同。
注:本文内容来自《Python语言及其应用》欢迎购买原书阅读