文件操作
open函数
写文件
我们使用 open 函数的写入模式来写文件:
f = open('test.txt', 'w')
f.write('hello world.')
f.close()
print(open('test.txt').read())
使用 w 模式时,如果文件不存在会被创建
除了写入模式,还有追加模式 a
读写模式 w+
f = open('test.txt', 'w+')
f.write('hello world. morning.')
f.seek(3)
print(f.read()) # hello world.
f.close()
读文件
使用 open 函数 来读文件,使用文件名的字符串作为输入参数:
默认打开文件是 ‘r’ 读模式
f = open("test.txt")
# 默认以读的方式打开文件,如果文件不存在会报错。
# 可以使用 read 方法来读入文件中的所有内容:
text = f.read()
print(text)
按照行 读入内容,readlines 方法返回一个列表,每个元素代表文件中每一行的内容:
f = open("test.txt")
lines = f.readlines()
print(lines)
f.close()
# 事实上,我们可以将 f 放在一个循环中,得到它每一行的内容:
f = open('test.txt')
for line in f:
print(line)
f.close()
上下文管理器
with open('my_file.txt', 'w') as fp:
data = fp.write("Hello world")
这等效于下面的代码,但是要更简便:
fp = open('my_file.txt', 'w')
try:
# do stuff with f
data = fp.write("Hello world")
finally:
fp.close()
自定义上下文管理器
比如可以这样定义一个简单的上下文管理器:
class ContextManager(object):
def __enter__(self):
print("Entering")
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
with ContextManager():
print("inside operate")
enter 的返回值
如果在 enter 方法下添加了返回值,
那么我们可以使用 as 把这个返回值传给某个参数:
class ContextManager2(object):
def __enter__(self):
print("Entering")
return "my value"
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
with ContextManager2() as val:
print(val)
一个通常的做法是将 enter 的返回值设为这个上下文管理器对象本身, 文件对象就是这样做的.
class ContextManager3(object):
def __enter__(self):
print("Entering")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
错误处理
上下文管理器对象将错误处理交给 exit 进行,可以将错误类型, 错误值和 traceback 等内容作为参数传递给 exit 函数:
class ContextManager4(object):
def __enter__(self):
print("Entering")
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
if exc_type is not None:
print(" Exception:", exc_value)
return True # 不想让错误抛出,只需要将 __exit__ 的返回值设为 True
with ContextManager4():
print(1 / 0)
import os
os.remove('my_file.txt')
二进制文件
二进制读写模式 b:
import os
f = open('binary.bin', 'wb')
f.write(os.urandom(10))
f.close()
f = open('binary.bin', 'rb')
print(repr(f.read()))
f.close()
with 方法
事实上,Python 提供了更安全的方法,当 with 块的内容结束后, Python 会自动调用它的 close 方法,确保读写的安全:
with open('new_file.txt', 'w') as f:
for i in range(3000):
x = 1.0 / (i - 1000)
f.write('hello world: ' + str(i) + '\n')
与 try/exception/finally 效果相同,但更简单。
查看文件写的结果,虽然触发 error,但已经写的内容是成功的。
!tail new_file.txt
!wc -l new_file.txt
# 删除文件:
import os
os.remove('test.txt')
os.remove('binary.bin')
os.remove('new_file.txt')
memoryview函数
函数主要用于需要高性能和低内存开销的场景,特别是在处理大型二进制数据时。它提供了一个窗口,让你能够直接、高效地访问和操作底层对象的内存,而不需要为数据创建昂贵的副本。
import struct
record_size = 4 + 8 # 12 bytes
with open("data.bin", "rb") as f:
# 假设我们只关心文件中的第100条记录
f.seek(99 * record_size) # 定位到第100条记录的起始位置
# 读取12个字节,并将其转换为 bytes 对象
record_bytes = f.read(record_size)
# 从 bytes 对象中解包数据
record_id, value = struct.unpack('<if', record_bytes) # 注意:这里会创建一个新的 bytes 对象
print(f"Record ID: {record_id}, Value: {value}")
这种方式在处理一条记录时是没问题的,但如果我们需要循环读取大量记录,并对它们进行修改,效率会很低,因为它每次都涉及到文件读取和字节对象的创建。
现在,我们使用 memoryview 来优化这个过程。我们一次性读取一个较大的块(例如100条记录),然后通过 memoryview 来“遍历”这个内存块,而不用创建新的 bytes 对象。
import struct
import array
# 假设 data.bin 文件已经存在并有足够的数据
# 我们可以先创建一个模拟的 data.bin 文件
with open("data.bin", "wb") as f:
for i in range(1000):
# 写入一个 int32 (i) 和一个 float64 (i * 1.5)
f.write(struct.pack('<id', i, i * 1.5))
# 实际应用,读取并处理文件
record_size = 12 # 4字节 (int) + 8字节 (double)
num_records_to_read = 100
with open("data.bin", "rb") as f:
# 一次性读取100条记录的字节数据到内存
buffer = f.read(num_records_to_read * record_size)
# 将 buffer 转换为一个可变的 bytearray,以便我们能通过 memoryview 修改它
byte_data = bytearray(buffer)
# 创建一个 memoryview 对象,指向这个 byte_data
# 重要的是,我们使用 struct 模块的格式字符串来指定 memoryview 的格式
# 这让 memoryview 知道如何解析这块内存
mv = memoryview(byte_data)
# 使用 struct 模块的格式字符串来创建一个结构化的 memoryview
# 这里的 'i' 是 int32, 'd' 是 double64
# 这使得我们可以像访问数组一样访问数据,但背后是 memoryview
# 'd' 表示双精度浮点数
structured_mv = mv.cast('id', shape=[num_records_to_read])
# 现在我们可以像访问数组一样,高效地访问和修改数据
for i in range(num_records_to_read):
record_id, value = structured_mv[i]
print(f"Record {i}: ID={record_id}, Value={value}")
# 如果需要,我们可以直接修改内存中的值
# 例如,将所有值的浮点数部分增加10
structured_mv[i] = (record_id, value + 10.0)
# 现在,如果我们将 byte_data 写回文件,修改就已经生效了
# 或者在内存中处理完后,将整个 byte_data 对象传递给其他函数,而不用担心数据拷贝