今天看到了一段代码,值得记录下来。这种写法一般会写在C语言里面做文件读写,很少会用到python之中。对于一个文件而言,如果是一个文本文件,按行读取可以直接使用python自带的方法,一般会用下面这种写法
with open(file_path, 'r') as fp: for line in fp: print(line)
在python里面可以直接对一个文件对象进行迭代,迭代的每一个结果都是文件中的每一行,这里分行是python本身来做的处理。如果是使用二进制模式打开,该怎么以流的形式读取一个文件中的每一行?
下面是一种非常优雅的写法:
# 一个使用字节模式打开的文件 fp = open(file_path, 'rb') def read_by_line(fp): # 每次读取的大小 1024字节 BLOCK_SIZE = 1024 # 保存最后一个换行符后面的内容 # 原因:因为只读取了BLOCK_SIZE个大小 # 最后一个换行符后面的内容的情况是未知的 # 在比特流的视角下 # xxxxxx \n xxxxxxx \n x xxxxxxxx # ---------------------* ———————— # 第一次读取 # 每一次读取都应该将*号处(即最后一个换行符后面的数据)的数据和下次读取的数据合并起来 # 这里 会有两种情况: # 1. 本次读取已经读完了所有数据 # 2. 本次没有读取完,还有下次读取 last_line = '' while True: data = fp.read(BLOCK_SIZE) # 每次读取的数据中有两种情况 # 第一种:data种包含换行符\n,可能包含一个也可能包含多个 # 第二种:data种不包含换行符\n if not data: # 所有数据都读取完了以后,中断循环 break # 每次读取都和上一次读取的数据中最后一个换行符\n后面的数据连接起来 data = "".join((last_line, data.decode())) # 将合并起来的数据再按照\n进行分割 lines = data.split('\n') # 将每次读取的数据最后一个换行符后面的数据都保存起来 # 以便于和下次的数据合并起来 last_line = lines.pop() # 最后一个换行符之前的数据是没有争议的 # 这里直接将他通过生成器返回出去 # 当然这里还会有两种情况 # 1. lines是一个空list, 因为前面的数据一直没有读取到换行符 # 所以每次的数据都被保留起来,和下次的数据合并起来 # 2. lines里面是有内容的,说明data中不止一个换行符\n # 这里可以将前几个换行符分割产生的行返回出去 # 因为他们绝对不会和后面的数据有联系 for line in lines: yield line # 所有的数据都读取完了,将最后一个换行符后的结果返回出去 yield last_line
当然,这里如果是一个可以用文本模式直接打开的文件,最好还是使用第一种方法,第二种方法的优点在于,可以直接从一个字节流中按行进行返回。
一个很有用的场景就是,将一个文本文件使用gzip压缩以后,在不解压的前提下,可以直接在压缩的字节流上直接进行按行读取。下面是一个很有用的例子,相对于上面的代码,只需要改动很小的一部分:
# 将fp文件跟改成gzip的open + import gzip + fp = gzip.open(file_path) + def decompress(chunk): + # 解压函数 + pass def read_by_line(fp): BLOCK_SIZE = 1024 last_line = '' while True: data = fp.read(BLOCK_SIZE) if not data: break # 在这里 - data = "".join((last_line, data.decode())) + data = "".join(last_line, decompress(data)) #需要对读取的数据做解压 lines = data.split('\n') last_line = lines.pop() for line in lines: yield line yield last_line