文件 I/O 操作
根据不同的操作系统正确对待文件内容
我们知道, Linux 系统下的回车符是 \n
Windows 下的是 \r\n
, 但是默认情况下内置的 open()
函数会把它们都转换为 \n
。有些情况你并不想这样,只想保持原来的,就需要使用一个关键字参数: newline
.
In [1]: f = open('hello.txt', 'r')
In [2]: f.read()
Out[2]: 'hello world!\n'
In [3]: win = open('hello.txt', 'r', newline='')
In [4]: win.read()
Out[4]: 'hello world!\r\n'
对一个已存在的文件,停止写操作,避免覆盖
import os
if not os.path.exists('same_file'):
with open('somefile', 'w') as f:
f.write('Hello xiguatian')
else:
print("File already exists")
其实,我们可以简单点儿
模式 x
可以在写入文件之前判断文件是否已经存在,假如存在则会抛出异常。
In [5]: with open('modx.txt', 'x', encoding='utf-8') as f:
...: f.write('test modx\n')
...:
In [6]: with open('modx.txt', 'x', encoding='utf-8') as f:
...: f.write('test modx\n')
...:
--------------------------------------------------------------
FileExistsError Traceback (most recent call last)
in ()
----> 1 with open('modx.txt', 'x', encoding='utf-8') as f:
2 f.write('test modx\n')
3
FileExistsError: [Errno 17] File exists: 'modx.txt'
所以我们可以在代码中使用异常处理即可。
In [7]: try:
...: with open('modx.txt', 'x', encoding='utf-8') as f:
...: f.write('test modx\n')
...: except FileExistsError as e:
...: print(e)
...:
[Errno 17] File exists: 'modx.txt'
In [8]:
如果文件是二进制的,使用 xb
来代替 x
制作一个只存在于内存中的文件对象
当你想模拟一个普通的文件的时候 io.StringIO
和 io.BytesIO
类是很有用的。比如,在 单元测试中,你可以使用 io.StringIO
来创建一个包含测试数据的类文件对象,这个对象可以被传给某个 以普通文件对象为参数的函数。
In [7]: rs = io.StringIO("Hello python3\n")
In [8]: rs.read()
Out[8]: 'Hello python3\n'
In [9]: s = io.StringIO()
In [10]: s.write("Hello world\n")
Out[10]: 12
In [11]: s
Out[11]: <_io.StringIO at 0x10ace93a8>
In [12]: s.getvalue()
Out[12]: 'Hello world\n'
如何直接读写压缩文件
这里说读写压缩文件指的不是创建一个压缩包,之后向里面添加普通文件;而是创建一个压缩格式的文件,之后向里面添加数据,就是普通的文本文字。读取也是一样的。
gzip
In [17]: import gzip
In [18]: with gzip.open('mygzip.gz', 'w') as gzf:
...: gzf.write(b'hello') # 注意这里接收到时二进制的文本内容
...:
In [19]: !ls
mygzip.gz # 被创建好的文件
上面创建好的压缩文件可以使用系统的 gzip
命令 进行直接解压,解压后的文件名是 mygzip
, 它是个普通文件
In [23]: !gzip -d mygzip.gz
In [24]: !ls -l my*
-rw-r--r-- 1 shark staff 1054896640 11 15 20:16 my_sharkyun.tar
-rw-r--r-- 1 shark staff 53 10 9 17:30 myapp_rc
-rw-r--r-- 1 shark staff 5 11 19 16:21 mygzip
In [25]: !cat mygzip
hello
当然也可以使用 gzip.open()
函数的 r
的模式读取。
bz2
In [33]: with bz2.open('mybz.bz2', 'w') as bzf:
...: bzf.write("这是个压缩文件的内容".encode("utf-8"))
...:
...:
In [34]: !ls mybz.bz2
mybz.bz2
In [35]: with bz2.open('mybz.bz2', 'r') as bzrf:
...: line = bzrf.read()
...: print(str(line,encoding='utf-8'))
...:
这是个压缩文件的内容
gzip
和 bz2
的 open()
函数和内置的 open()
函数有几乎一样的参数,唯一不同的是, 操作内置的 open()
函数默认打开模式是普通文本 t
, gzip
和 bz2
的 open()
函数默认的是二进制 b
(bytes)。
所以要想直接操作普通文本,不进行转换需要使用 wt
或 rt
模式。
如:
In [39]: with bz2.open('mybz.bz2', 'wt') as bzf:
...: bzf.write("这是个压缩文件的内容")
...:
对了,当进行写入操作的时候可以指定压缩级别,默认级别是 9
最高压缩比例,但是效率最慢。
要指定其他的压缩级别可以使用 compresslevel
关键字参数。
with gzip.open('somefile.gz', 'wt', compresslevel=5) as f:
f.write(text)
打开视频文件或者图片文件的正确姿势
假如你的用户要上传一部大小为 5G
的电影, 你该如何把它转存到你的硬盘。
我们首先说一下流程:
- 先读取到文件的内容
- 之后打开一个新的文件(写模式)
- 把读到的内容写入到新的文件内
这样就把上传的文件写入到本地的硬盘了。
可是当一个文件有 5G
之大,你不会想要直接全部读取到内存中的,但是它是视频文件,必须一字节的方式读取和写入。
所以我们可以以一个固定长度来迭代的方式进行读取和写入。
from functools import partial
RECORD_SIZE = 32
with open('somefile.data', 'rb') as f:
records = iter(partial(f.read, RECORD_SIZE), b'')
for r in records:
...
内置的 iter()
函数会把一个可迭代对象转换为一个迭代器,这样可以节省内存。
这里的 records
对象是一个可迭代对象,它会不断的产生固定大小的数据 块,直到文件末尾。要注意的是如果总记录大小不是块大小的整数倍的话,最后一个返 回元素的字节数会比期望值少。
partial()
用于创建一个可调用对象, 这个可调用对象在每次被调用时会从文件中读取固定数目的字节。
创建临时文件或目录, 文件关闭后自动销毁
tempfile
模块中有很多的函数可以完成这些任务。
创建一个匿名的临时文件
from tempfile import TemporaryFile
with TemporaryFile('w+') as f:
# 写内容到文件
f.write('Hello World\n')
f.write('Testing\n')
# 调整文件内部指针到文件的开头位置
f.seek(0)
# 读取文件内容
data = f.read()
TemporaryFile()
的第一个参数是文件打开的模式,在这里通常文本模式使用 w+
,二进 制模式使用 wb+
。这种带 +
的模式是同时支持读和写操作的,在这里是很有用的,因为当你关闭 文件后再去改变模式的时候,文件实际上已经不存在了。
TemporaryFile()
另外还支持跟内置的 open()
函数一样的其他参数。
比如: encoding
等
创建有文件名的临时文件
NamedTemporaryFile()
会为我们创建一个有文件名的临时文件,但同样,此文件会在文件关闭时会被自动删除掉。
In [51]: from tempfile import NamedTemporaryFile
In [52]: with NamedTemporaryFile('w+') as f:
...: print('filename is:', f.name)
...:
filename is: /var/folders/77/cwcr40m52z72xhbql18yvt740000gn/T/tmpvki4ejzd