python io简单总结

本文详细介绍了Python中文件的读写操作,包括如何处理各种编码,以及在不同模式下(如文本模式和二进制模式)的操作。强调了二进制数据的处理,如字节索引和迭代,以及使用memoryview进行零复制操作。此外,还讨论了读写压缩文件(gzip和bz2)以及内存映射文件的技术,强调了在处理大文件或需要随机访问时的高效性。文章最后提到了使用iter和functools.partial进行固定大小数据块的迭代,展示了灵活处理文件内容的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、文件的读写操作默认使用系统编码,可以通过调用 sys.getdefaultencoding() 来得到。在大多数机器上面都是 utf-8 编码。如果你已经知道你要读写的文本是其他编码方式,那么可以通过传递一个可选的 encoding 参数给 open()

with open('somefile.txt', 'rt', encoding='latin-1') as f:
	...

Python 支持非常多的文本编码。几个常见的编码是 ascii, latin-1, utf-8 和 utf-16。在 web 应用程序中通常都使用的是 UTF-8。ascii 对应从 U+0000 到 U+007F 范围内的 7 位字符。latin-1 是字节 0-255 到 U+0000 至 U+00FF 范围内 Unicode 字符的直接映射。当读取一个未知编码的文本时使用 latin-1 编码永远不会产生解码错误。使用latin-1 编码读取一个文件的时候也许不能产生完全正确的文本解码数据,但是它也能从中提取出足够多的有用数据。同时,如果你之后将数据回写回去,原先的数据还是会保留的。

2、模式:

t   是windows平台特有的所谓text mode(文本模式),区别在于会自动识别windows平台的换行符
rt  模式下,python在读取文本时会自动把\r\n转换成\n

打印输出至文件中, 你想将 print() 函数的输出重定向到一个文件中去(关于输出重定向到文件中就这些了。但是有一点要注意的就是文件必须是以文本模式打开。如果文件是二进制模式的话,打印就会出错。)

with open('d:/work/test.txt', 'wt') as f:
	print('Hello World!', file=f)

3、在读取二进制数据的时候,字节字符串和文本字符串的语义差异可能会导致一个潜在的陷阱。特别需要注意的是,索引和迭代动作返回的是字节的值而不是字节字符串。

t = 'Hello World'
print(t[0])

b = b'Hello World'
print(b[0])

如果你想从二进制模式的文件中读取或写入文本数据,必须确保要进行解码和编码操作。比如:

with open('somefile.bin', 'rb') as f:
	data = f.read(16)
	text = data.decode('utf-8')

with open('somefile.bin', 'wb') as f:
	text = 'Hello World'
	f.write(text.encode('utf-8'))

二进制 I/O 还有一个鲜为人知的特性就是数组和 C 结构体类型能直接被写入,而不需要中间转换为自己对象。

nums = array.array('i', [1, 2, 3, 4])
with open('data.bin', 'wb') as f:
    f.write(nums)

这个适用于任何实现了被称之为” 缓冲接口” 的对象,这种对象会直接暴露其底层的内存缓冲区给能处理它的操作。二进制数据的写入就是这类操作之一。
很多对象还允许通过使用文件对象的 readinto() 方法直接读取二进制数据到其底层的内存中去。比如:

a = array.array('i', [0, 0, 0, 0, 0, 0, 0, 0])
with open('data.bin', 'rb') as f:
    f.readinto(a)

print(a)

但是使用这种技术的时候需要格外小心,因为它通常具有平台相关性,并且可能会依赖字长和字节顺序 (高位优先和低位优先)

你想直接读取二进制数据到一个可变缓冲区中,而不需要做任何的中间复制操作。或者你想原地修改数据并将它写回到一个文件中去。

def read_into_buffer(filename):
    buf = bytearray(os.path.getsize(filename))
    with open(filename, 'rb') as f:
        f.readinto(buf)
    return buf


with open('sample.bin', 'wb') as f:
    f.write(b'Hello World')

buf = read_into_buffer('sample.bin')
print(buf)

with open('newsample.bin', 'wb') as f:
    f.write(buf)

文件对象的 readinto() 方法能被用来为预先分配内存的数组填充数据,甚至包括由 array 模块或 numpy 库创建的数组。和普通 read() 方法不同的是, readinto() 填充已存在的缓冲区而不是为新对象重新分配内存再返回它们。因此,你可以使用它来避免大量的内存分配操作。比如,如果你读取一个由相同大小的记录组成的二进制文件时,你可以像下面这样写:

record_size = 32 # Size of each record (adjust value)
buf = bytearray(record_size)
with open('somefile.bin', 'rb') as f:
	while True:
		n = f.readinto(buf)
		if n < record_size:
			break
		# Use the contents of buf
		

另外有一个有趣特性就是 memoryview ,它可以通过零复制的方式对已存在的缓冲区执行切片操作,甚至还能修改它的内容。比如:

def read_into_buffer(filename):
    buf = bytearray(os.path.getsize(filename))
    with open(filename, 'rb') as f:
        f.readinto(buf)
    return buf


with open('sample.bin', 'wb') as f:
    f.write(b'Hello World')

buf = read_into_buffer('sample.bin')
print(buf)

m1 = memoryview(buf)
m2 = m1[-5:]
print(m2)
m2[:] = b'WORLD'
print(buf)

使用 f.readinto() 时需要注意的是,你必须检查它的返回值,也就是实际读取的字节数。如果字节数小于缓冲区大小,表明数据被截断或者被破坏了 (比如你期望每次读取指定数量的字节)。

4、读写压缩文件
读写一个 gzip 或 bz2 格式的压缩文件
gzip 和 bz2 模块可以很容易的处理这些文件。两个模块都为 open() 函数提供了另外的实现来解决这个问题。比如,为了以文本形式读取压缩文件。

with gzip.open('somefile.gz', 'wt') as f:
    f.write('Hello World')


with gzip.open('somefile.gz', 'rt') as f:
    text = f.read()

print(text)

with bz2.open('somefile.bz2', 'wt') as f:
    f.write(text)

with bz2.open('somefile.bz2', 'rt') as f:
    text = f.read()
print(text)

如上,所有的 I/O 操作都使用文本模式并执行 Unicode 的编码/解码。类似的,如果你想操作二进制数据,使用 rb 或者 wb 文件模式即可

大部分情况下读写压缩数据都是很简单的。但是要注意的是选择一个正确的文件模式是非常重要的。如果你不指定模式,那么默认的就是二进制模式,如果这时候程序想要接受的是文本数据,那么就会出错。 gzip.open() 和 bz2.open() 接受跟内置的open() 函数一样的参数,包括 encoding,errors,newline 等等。
当写入压缩数据时,可以使用 compresslevel 这个可选的关键字参数来指定一个压缩级别。比如:

with gzip.open('somefile.gz', 'wt', compresslevel=5) as f:
	f.write(text)
	

默认的等级是 9,也是最高的压缩等级。等级越低性能越好,但是数据压缩程度也越低。

最后一点, gzip.open() 和 bz2.open() 还有一个很少被知道的特性,它们可以作用在一个已存在并以二进制模式打开的文件上。比如,下面代码是可行的:

import gzip


f = open('somefile.gz', 'rb')

with gzip.open(f, 'rt') as g:
	text = g.read()
	

这样就允许 gzip 和 bz2 模块可以工作在许多类文件对象上,比如套接字,管道和内存中文件等。

5、你想在一个固定长度记录或者数据块的集合上迭代,而不是在一个文件中一行一行的迭代。可以使用 iter 和 functools.partial()

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:
        print(r)

这个例子中的 records 对象是一个可迭代对象,它会不断的产生固定大小的数据块,直到文件末尾。要注意的是如果总记录大小不是块大小的整数倍的话,最后一个返回元素的字节数会比期望值少。

iter() 函数有一个鲜为人知的特性就是,如果你给它传递一个可调用对象和一个标记值,它会创建一个迭代器。这个迭代器会一直调用传入的可调用对象直到它返回标记值为止,这时候迭代终止。

在例子中, functools.partial 用来创建一个每次被调用时从文件中读取固定数目字节的可调用对象。标记值 b’’ 就是当到达文件结尾时的返回值。

最后再提一点,上面的例子中的文件时以二进制模式打开的。如果是读取固定大小的记录,这通常是最普遍的情况。而对于文本文件,一行一行的读取 (默认的迭代行为) 更普遍点。

6、内存映射的二进制文件
你想内存映射一个二进制文件到一个可变字节数组中,目的可能是为了随机访问它的内容或者是原地做些修改。
使用 mmap 模块来内存映射文件。下面是一个工具函数,向你演示了如何打开一个文件并以一种便捷方式内存映射这个文件。

def memory_map(filename, access=mmap.ACCESS_WRITE):
    size = os.path.getsize(filename)
    fd = os.open(filename, os.O_RDWR)
    return mmap.mmap(fd, size, access=access)


size_data = 1000000
with open('data', 'wb') as f:
    f.seek(size_data - 1)
    f.write(b'\x00')


m = memory_map('data')
print(len(m),  m[0])
m[0:11] = b'Hello World'

with open('data', 'rb') as f:
    print(f.read(11))
    

mmap() 返回的 mmap 对象同样也可以作为一个上下文管理器来使用,这时候底层的文件会被自动关闭。比如

with memory_map('data') as m:
	print(len(m))
	print(m[0:10])

默认情况下, memeory map() 函数打开的文件同时支持读和写操作。任何的修改内容都会复制回原来的文件中。如果需要只读的访问模式,可以给参数 access 赋值为mmap.ACCESS READ 。比如:

m = memory_map(filename, mmap.ACCESS_READ)

如果你想在本地修改数据,但是又不想将修改写回到原始文件中,可以使用mmap.ACCESS COPY :

m = memory_map(filename, mmap.ACCESS_COPY)

为了随机访问文件的内容,使用 mmap 将文件映射到内存中是一个高效和优雅的方法。例如,你无需打开一个文件并执行大量的 seek() , read() , write() 调用,只需要简单的映射文件并使用切片操作访问数据即可。
一般来讲, mmap() 所暴露的内存看上去就是一个二进制数组对象。但是,你可以使用一个内存视图来解析其中的数据。比如:

m = memory_map('data')
# Memoryview of unsigned integers
v = memoryview(m).cast('I')
v[0] = 7
print(m[0:4])
 

需要强调的一点是,内存映射一个文件并不会导致整个文件被读取到内存中。也就是说,文件并没有被复制到内存缓存或数组中。相反,操作系统仅仅为文件内容保留了一段虚拟内存。当你访问文件的不同区域时,这些区域的内容才根据需要被读取并
映射到内存区域中。而那些从没被访问到的部分还是留在磁盘上。所有这些过程是透明的,在幕后完成!

如果多个 Python 解释器内存映射同一个文件,得到的 mmap 对象能够被用来在解释器直接交换数据。也就是说,所有解释器都能同时读写数据,并且其中一个解释器所做的修改会自动呈现在其他解释器中。很明显,这里需要考虑同步的问题。但是这种方法有时候可以用来在管道或套接字间传递数据。

要注意的是使用 mmap() 函数时会在底层有一些平台的差异性。另外,还有一些选项可以用来创建匿名的内存映射区域。参考:https://docs.python.org/3/library/mmap.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值