Это можно сократить до нескольких строк кода, используя функции grouper
и roundrobin
, предоставляемые документацией модуля itertools
.
import contextlib
from itertools import zip_longest, cycle, islice, chain
# Define grouper() and roundrobin() here
with contextlib.ExitStack() as stack:
# Open each file *once*; the exit stack will make sure they get closed
files = [stack.enter_context(open(fname)) for frame in process]
# Instead of iterating over each file line by line, we'll iterate
# over them in 8-line batches.
groups = [grouper(f, 8) for f in files]
# Interleave the groups by taking an 8-line group from one file,
# then another, etc.
interleaved = roundrobin(*groups)
# *Then* flatten them into a stream of single lines
flattened = chain.from_iterable(interleaved)
# Filter out the None padding added by grouper() and
# read the lines into a list
lines = list(filter(lambda x: x is not None, flattened))
Обратите внимание, что пока вы не позвоните list
, вы на самом деле ничего не читаете из файлов; вы просто создаете функциональный конвейер, который будет обрабатывать ввод по требованию.
Для справки, это определения grouper
и roundrobin
, скопированные из документации .
# From itertools documentation
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
# From itertools documentation
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
num_active = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
while num_active:
try:
for next in nexts:
yield next()
except StopIteration:
# Remove the iterator we just exhausted from the cycle.
num_active -= 1
nexts = cycle(islice(nexts, num_active))