第五章:文件与 IO
5.1 读写文本数据
文本文件中的 回车 在不同操作系统中所用的字符表示有所不同。
- Windows : \r\n
- Linux/Unix : \n
- Mac OS : \r
rt模式下,python在读取文本时会自动把\r\n转换成\n.
1 | with open('somefile.txt', 'rt') as f: |
其实python默认就会将这些文件的换行符全部统一成
\n
如果不想使用默认,可以使用open函数的newline
参数
1
2 with open('somefile.txt', 'rt', newline='') as f:
pass
print语句有一个file参数,用于重定向数据流
1 | print(line1, file=f) |
- 当读取一个未知编码的文本时使用 latin-1 编码永远不会产生解码错误。
- 使用latin-1 编码读取一个文件的时候也许不能产生完全正确的文本解码数据,但是它也能从中提取出足够多的有用数据。
5.2 打印输出至文件中
print() 函数的file 关键字参数
1 | with open('d:/work/test.txt', 'wt') as f: |
5.3 使用其他分隔符或行终止符打印
print() 函数中使用 sep 和 end 关键字参数
5.4 读写字节数据
用模式为 rb 或 wb 的 open() 函数
5.5 文件不存在才能写入
你想像一个文件中写入数据,但是前提必须是这个文件在文件系统上不存在。也就是不允许覆盖已存在的文件内容。
open() 函数中使用 x 模式来代替 w 模式
1 | with open('test.txt', 'xt') as f: |
当然也可以自己实现:
1 | import os |
如果文件是二进制的,使用 xb
5.6 字符串的 I/O 操作
操作类文件对象
的程序来操作文本或二进制字符串。
使用 io.StringIO() 和 io.BytesIO() 类来创建类文件对象操作字符串数据。
- StringIO的行为与file对象非常像,但它不是磁盘上文件,而是一个内存里的“文件”,我们可以像操作磁盘文件那样来操作StringIO。
- StringIO模块,主要用于在内存缓冲区中读写数据。
- 数据读写不一定是文件,也可以在内存中进行。
- StringIO 顾名思义就是在内存中以 io 流的方式读写 str。
1 | from io import StringIO |
s=io.StringIO([buf])
此实例类似于open方法,不同的是==它并不会在硬盘中生成文件,而只寄存在缓冲区==;
可选参数buf是一个str或unicode类型。它将会与其他后续写入的数据存放在一起
(注意,若要在初始化数据之后继续写入数据,则在写入数据之前,应先将读写位置移动到结尾,然后再写入,否则,初始化数据会被覆盖掉,因为读写位置默认是0)。
s.read([n])
- 参数n限定读取长度,int类型;
- 缺省状态为从当前读写位置读取对象s中存储的所有数据。
- 读取结束后,读写位置被移动。
s.readline([length])
- 参数length限定读取的结束位置,int类型,
- 缺省状态为None:从当前读写位置读取至下一个以“\n”为结束符的当前行。
- 读写位置被移动。
s.readlines([sizehint])
- 参数sizehint为int类型,
- 缺省状态为读取所有行并作为列表返回,如果不使用缺省值从当前读写位置读取至下一个以“\n”为结束符的当前行。
- 读写位置被移动。
s.write(s)
- 从读写位置将参数s写入给对象s。
- 参数s为str或unicode类型。读写位置被移动
s.writelines(list)
- 从读写位置将list写入给对象s。参数list为一个列表,列表的成员为str或unicode类型。
- 读写位置被移动。
s.getvalue()
此函数没有参数,无论读写位置在哪里,都能够返回对象s中的所有数据。s.truncate([size])
- 如果有size参数 :
无论读写位置在哪里,都从起始位置开始,裁剪size字节的数据。 - 不带size参数
将当前读写位置之前的数据,裁剪下来。
- 如果有size参数 :
s.tell()
返回当前读写位置。s.seek(pos[,mode])
- 移动当前读写位置至pos处,
- 可选参数mode为0时将读写位置移动至pos处,
为1时将读写位置从当前位置起向前或向后移动|pos|个长度,
为2时将读写位置置于末尾处再向前或向后移动|pos|个长度;
mode的默认值为0。
s.close()
释放缓冲区,执行此函数后,数据将被释放,也不可再进行操作。s.isatty()
此函数总是返回0。s.flush()
刷新内部缓冲区。
示例:
1
2
3
4
5
6
7
8
9
10
11
12 from io import StringIO
s = StringIO('python')
s.seek(0,2) #将读写位置移动到结尾
s.write("aaaa")
lines = ['xxxxx', 'bbbbbbb']
s.writelines(lines)
s.write("ttttttttt")
print(s.getvalue())
# pythonaaaaxxxxxbbbbbbbttttttttt
io.StringIO 只能用于文本。如果你要操作二进制数据,要使用 io.BytesIO 类
- 当你想模拟一个普通的文件的时候 StringIO 和 BytesIO 类是很有用的。
- 比如,在单元测试中,你可以使用 StringIO 来创建一个包含测试数据的类文件对象,这个对象可以被传给某个参数为普通文件对象的函数。
5.7 读写压缩文件
读写一个 gzip 或 bz2 格式的压缩文件。
使用gzip 和 bz2 模块
1 | import gzip |
gzip.open() 和 bz2.open() 接受跟内置的open() 函数一样的参数,包括 encoding,errors,newline 等等。
当写入压缩数据时,可以使用 compresslevel 这个可选的关键字参数来指定一个压缩级别。
1 | with gzip.open('somefile.gz', 'wt', compresslevel=5) as f: |
默认的等级是 9,也是最高的压缩等级。等级越低性能越好,但是数据压缩程度也越低。
5.8 固定大小记录的文件迭代
在一个固定长度记录或者数据块的集合上迭代,而不是在一个文件中一行一行的迭代。
使用 iter 和 functools.partial() 函数:
1 | from functools import partial |
records 对象是一个可迭代对象,它会不断的产生固定大小的数据块,直到文件末尾。
注意:
如果总记录大小不是块大小的整数倍的话,最后一个返回元素的字节数会比期望值少。
iter() 函数有一个鲜为人知的特性就是,如果你给它传递一个可调用对象和一个
标记值,它会创建一个迭代器。这个迭代器会一直调用传入的可调用对象直到它返回标记值为止,这时候迭代终止。
固定大小记录的文件迭代
在二进制模式更常见
5.9 读取二进制数据到可变缓冲区中
为了读取数据到一个可变数组中,使用文件对象的 readinto() 方法。
file.readinto(buf,size) : 读取size个字节到文件缓冲器中
- 和普通 read() 方法不同的是,==readinto() 填充已存在的缓冲区而不是为新对象重新分配内存再返回它们。==
- 因此,你可以使用它来避免大量的内存分配操作。
1 | import os.path |
另外有一个有趣特性就是 memoryview ,它可以通过零复制的方式对已存在的缓冲区执行切片操作,甚至还能修改它的内容。
1 | print(buf) # bytearray(b'Hello World') |
5.10 内存映射的二进制文件
内存映射一个二进制文件到一个可变字节数组中,目的可能是为了随机访问它的内容或者是原地做些修改。
使用 mmap 模块来内存映射文件。
mmap : memory-map打开一个文件并以一种便捷方式内存映射这个文件。
mmap是一种虚拟内存映射文件的方法,即可以将一个文件或者其它对象映射到进程的地址空间,实现==文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。==
普通文件被映射到虚拟地址空间后,程序可以像操作内存一样操作文件,可以提高访问效率,适合处理超大文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import mmap
# 预先准备一个hello.txt文件
with open("hello.txt", "wb") as f:
f.write("Hello Python!\n")
with open("hello.txt", "r+b") as f:
# 内存映射文件,0表示整个文件
mm = mmap.mmap(f.fileno(), 0)
# 通过标准文件方法读取内容
print(mm.readline()) # Hello Python!
# 通过切片读取内容
print(mm[:5]) # Hello
# 使用切片更新内容
# 注意,新内容的大小必须相同
mm[6:] = " world!\n"
# 使用标准文件方法重新读取
mm.seek(0)
print(mm.readline()) # Hello world!
# 关闭
mm.close()
1 | import os |
- 需要强调的一点是,内存映射一个文件并不会导致整个文件被读取到内存中。
- 也就是说,文件并没有被复制到内存缓存或数组中。相反,操作系统仅仅为文件内容保留了一段虚拟内存。
- 当你访问文件的不同区域时,这些区域的内容才根据需要被读取并映射到内存区域中。而那些从没被访问到的部分还是留在磁盘上。所有这些过程是透明的,在幕后完成!
5.11 文件路径名的操作
使用路径名来获取文件名,目录名,绝对路径等等。
使用 os.path 模块中的函数来操作路径名。
1 | import os |
os.path 模块知道 Unix 和 Windows 系统之间的差异并且能够可靠地处理类似 Data/data.csv 和Data\data.csv 这样的文件名。
5.12 测试文件是否存在
- os.path.exists()
- os.path.isdir()
- os.path.isfile()
- os.path.isllink()
- os.path.realpath() : 对于link,找出链接的真正地址
注意:
os.path.isfile()
,os.path.isdir()
,os.path.isllink()
,是能否找到这个文件/目录/链接.不是判断传入的参数是不是文件/目录/链接
1 | import os |
获取文件/目录的一些数据
- os.path.getsize : 获取大小
- os.path.getmtime : 获取修改时间
1 | import os.path |
5.13 获取文件夹中的文件列表
获取文件系统中某个目录下的所有文件/目录列表。
os.listdir() 函数
1 | import os |
注意:
如果trainingfile目录下的,test目录下还有文件.那么这些文件并不能出现在os.listdir
里面
当然,我们可以自己写一个:
1
2
3
4
5
6
7
8
9
10
11
12 import os
def listdirs(path):
for f_or_d in os.listdir(path):
if os.path.isdir(f_or_d):
yield from listdirs(f_or_d)
else:
yield f_or_d
x = list(listdirs(r'D:\note\Python\书籍__python-cookbook\trainingfile'))
print(x)
# ['data', 'hello.txt', 'setup.py', 'somefile.txt', 'test.txt', 'test_func.py', '__pycache__', 'training1.py', 'training2.py', 'training3.py', 'training4.py', 'training5.py', 'training6.py']
1 | import os |
也可以使用字符串的 startswith() 和 endswith() 方法过滤目录内容:
1 | import os |
使用os.stat() 函数来收集文件数据
1 | import os |
5.14 忽略文件名编码
sys.getfilesystemencoding() 返回的文本编码来编码或解码。
1 | import sys |
5.15 打印不合法的文件名
使用下面的方法可以避免这样的错误:
1 | def bad_filename(filename): |
5.16 增加或改变已打开文件的编码
在不关闭一个已打开的文件前提下增加或改变它的 Unicode 编码。
给一个以二进制模式打开的文件添加 Unicode 编码/解码方式,可以使用io.TextIOWrapper()
对象包装它。
1 | f = open('hello.txt','w') |
- io.TextIOWrapper 是一个编码和解码 Unicode 的文本处理层,
- io.BufferedWriter 是一个处理二进制数据的带缓冲的 I/O 层,
- io.FileIO 是一个表示操作系统底层文件描述符的原始文件。
- 增加或改变文本编码会涉及增加或改变最上面的io.TextIOWrapper 层。
1 | import io |
如果你想修改一个已经打开的文本模式的文件的编码方式,可以先使用 detach()方法移除掉已存在的文本编码层,并使用新的编码方式代替。
1 | import sys |
5.17 将字节写入文本文件
在文本模式打开的文件中写入原始的字节数据。
将字节数据直接写入文件的缓冲区
1 | import sys |
类似的,能够通过读取文本文件的 buffer 属性来读取二进制数据
5.18 将文件描述符包装成文件对象
你有一个对应于操作系统上一个已打开的 I/O 通道 (比如文件、管道、套接字等)的整型文件描述符,你想将它包装成一个更高层的 Python 文件对象。
(看不懂,略)
5.19 创建临时文件和文件夹
创建一个临时文件或目录,并希望使用完之后可以自动销毁掉。
使用tempfile模块
tempfile.TemporaryFile :
- 创建一个匿名的临时文件
- TemporaryFile() 支持跟内置的 open() 函数一样的参数
1 | from tempfile import TemporaryFile |
如果要创建一个具名的临时文件,使用NamedTemporaryFile()
1 | from tempfile import NamedTemporaryFile |
临时文件用完即删,但是也可以不删除,使用delete
参数
1 | with NamedTemporaryFile('w+t', delete=False) as f: |
同理还有临时目录:
1 | from tempfile import TemporaryDirectory |
5.20 与串行端口的数据通信
对于串行通信最好的选择是使用 pySerial 包 。
1 | import serial |
5.21 序列化 Python 对象
使用 pickle 模块
1 | import pickle |
1 | import pickle |
**千万不要对不信任的数据使用 pickle.load()**。
pickle 在加载时有一个副作用就是它会自动加载相应模块并构造实例对象。
但是某个坏人如果知道 pickle 的工作原理,
他就可以创建一个恶意的数据导致 Python 执行随意指定的系统命令。
因此,一定要保证 pickle 只在相互之间可以认证对方的解析器的内部使用。
有些类型的对象是不能被序列化的。
这些通常是那些依赖外部系统状态的对象,比如打开的文件,网络连接,线程,进程,栈帧等等。
用户自定义类可以通过提供
__getstate__()
和__setstate__()
方法来绕过这些限制。如果定义了这两个方法,pickle.dump() 就会调用
__getstate__()
获取序列化的对象。类似的,__setstate__()
在反序列化时被调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 # countdown.py
import time
import threading
class Countdown:
def __init__(self, n):
self.n = n
self.thr = threading.Thread(target=self.run)
self.thr.daemon = True
# 实例化就启动线程
self.thr.start()
def run(self):
while self.n > 0:
print('T-minus', self.n)
self.n -= 1
time.sleep(5)
# 序列化调用
def __getstate__(self):
return self.n
# 反序列化调用
def __setstate__(self, n):
self.__init__(n)就像前面说的:
==pickle.load()会自动加载相应模块并构造实例对象==,对于Countdown类来说,创建实例对象就触发了self.thr.start()
,也就启动了线程注意这里
__getstate__
存储的是self.n
,而self.n是线程结束的关键.
也就是说,存储self.n能存储线程的状态,使用pickle.load()
之后能继续执行线程