Создать генератор из строк нескольких файлов

1

У меня есть главный файл и набор вспомогательных файлов, но я не знаю названия вспомогательных файлов, пока не посмотрю в главном файле.

Главный файл содержит два столбца: некоторые данные и второе имя файла, например,

data1_from_master   hidden_file1
data2_from_master   hidden_file2
data3_from_master   hidden_file1
data4_from_master   hidden_file3
data5_from_master   hidden_file1

Я хочу создать генератор, который дает элемент из первого столбца основного файла, а затем строку данных из одного из вспомогательных файлов. Например,

data1_from_master    line1_from_file1
data2_from_master    line1_from_file2
data3_from_master    line2_from_file1
data4_from_master    line1_from_file3
data5_from_master    line3_from_file1

Количество строк в главном файле равно сумме количества строк во всех дочерних файлах, поэтому после того, как мастер файл пройден, все вспомогательные файлы также пройдут.

Если бы у меня было только два файла, которые я хотел открыть, и я знал их имена заранее, я мог бы сделать что-то вроде этого.

with open(master_file, 'r') as a, open(hidden_file, 'r') as b:
    for line1, line2 in zip(a, b):
        yield (line1, line2)

Но дилемма заключается в том, что пока я не прочитал заданную строку основного файла, я не знаю, какой вспомогательный файл следует читать. А потом добавлена сложность создания генератора, где строки нескольких разных файлов.

Теги:
file
generator
nested
yield

2 ответа

3
Лучший ответ

Вы хотите использовать ExitStack. Это вспомогательный класс, предоставляемый библиотекой contextlib позволяющий комбинировать контекстные менеджеры. Он может использоваться для сохранения нескольких файлов в одном выражении with оператора.

from contextlib import ExitStack

def iter_master_file(filename):
    with ExitStack() as stack:
        master = stack.enter_context(open(filename))
        hidden_files = {}

        for line in master:
            # You can parse the lines as you like
            # Here I just assume the last word is a file name
            *data, file = line.split()

            if file not in hidden_files:
                hidden_files[file] = stack.enter_context(open(file))

            yield ' '.join(data), next(hidden_files[file]).strip()

пример

Позвольте настроить несколько файлов для этого примера.

файлы

master.txt

master says hidden1.txt is: hidden1.txt
master says hidden2.txt is: hidden2.txt
master says hidden1.txt is: hidden1.txt
master says hidden2.txt is: hidden2.txt

hidden1.txt

I am hidden file 1 line 1
I am hidden file 1 line 2

hidden2.txt

I am hidden file 2 line 1
I am hidden file 2 line 2

Вот пример.

Код

for data, hidden_data in iter_master_file('master.txt'):
    print(data, hidden_data)

Выход

master says hidden1.txt is: I am hidden file 1 line 1
master says hidden2.txt is: I am hidden file 2 line 1
master says hidden1.txt is: I am hidden file 1 line 2
master says hidden2.txt is: I am hidden file 2 line 2
  • 0
    Потрясающий ответ. Спасибо.
0

Вы можете сохранить "кеш" открытых файлов и вызвать fileobj.readline() при необходимости:

def read_master_file(master):
    other_files = {}
    for line in master:
        data, name = line.split()
        if name not in otherfiles:
            other_files[name] = open(name)
        yield data, other_files[name].readline()
    for f in other_files.values():
        f.close()

Используется в качестве:

with open('master') as master:
    for data, line in read_master_file(master):
        # do stuff

Это один из тех случаев, когда вы должны использовать файлы без with, к сожалению, так как вы не знаете, сколько файлов вы будете иметь дело с.

Вы можете написать собственный менеджер контекста, чтобы сохранить "кеш", чтобы добиться чего-то вроде:

def read_master_file(master):
    with OtherFiles() as other_files:
        for line in master:
            data, name = line.split()
            yield data, other_files.get_file(name).readline()

Где get_file будет искать кеш и, возможно, открыть файл, а __exit__ метода OtherFiles() закроет открытые файлы.

Но если это единственное место, где он будет использоваться, это не имеет смысла.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню