问题:假设有多个 行数一样 的文件,如何使用 python 同时对这些文件按行进行读取?
方案一:
解决这个问题需要用到 python 的生成器,实现代码如下:
filenames = ['a.txt', 'b.txt', 'c.txt']
def gen_line(fname):
with open(fname, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
gens = [gen_line(fname) for fname in filenames]
for a, b, c in zip(*gens):
print('\t'.join([a, b, c]))
示例代码中的操作可分为四个步骤:
- 将文件名放入一个
list
- 定义一个按行读取文件的生成器
- 使用使用列表推导式生成一个生成器的列表(这里也可以换用生成器表达式)
- 使用
*
语法拆包,然后使用zip
进行迭代输出
注意:如果文件的行数不一致,zip
会在迭代完最少行数的文件后退出,且不会有任何异常抛出。如需要保证迭代完所有文件内容,可使用 itertools.zip_longest
替代。
方案二:
可能会有人觉得上面的解决方案有一点啰嗦,下面提供一个更加精简的解决方案,此方案来自 stackoverflow 上面的一篇回答:
from contextlib import ExitStack
filenames = ['a.txt', 'b.txt', 'c.txt']
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
for rows in zip(*files):
print('\t'.join([x.strip() for x in rows]))
这个方案使用了python 3.3
引入的 contextlib.ExitStack
用于管理文件关闭操作。代码看起来比方案一精简了不少。不过,如果让我来选择的话,我更倾向于使用方案一。原因如下:
- 虽然我非常喜欢精简的代码,行数越少的代码我越喜欢。但是,代码的可读性更为重要。方案二使用了一个并不那么普及的方法,或者说所谓的“高级特性”,代码确实简洁了不少,但却大大降低了代码的可读性,不可取。代码是写给人看的,机器只负责执行它。推荐一篇文章《为什么高级程序员写的代码都是傻瓜式的?》。一句话,可读性优于代码精简。
- 方案二虽然精简,但真正用起来可能并没有方案一方便,看一下
strip
方法的调用位置就能明白。方案一之所以显得啰嗦,是因为专门为打开文件定义了一个生成器。但是,这个生成器可以在其它任何需要打开文件的地方复用,而且用起来非常方便。也许将来某一天,类似的生成器会直接进入python
的标准库也说不定。 - 即便是完全使用
python 3
,版本的兼容性在很多情况下依然是要需要考虑的。 - 方案一是我自己想出来的,当然会觉得好。(皮一下,很开心!)
最后,以上仅为个人观点,方案二其实也挺好的。