2013-04-19 2 views
12

Я хочу обрабатывать много файлов, как если бы они были одним файлом. Каков правильный питонический способ взять [filenames] => [file objects] => [lines] с генераторами/не считывать весь файл в память?Какой самый pythonic способ перебрать все строки из нескольких файлов?

Мы все знаем, что правильный способ открыть файл:

with open("auth.log", "rb") as f: 
    print sum(f.readlines()) 

И мы знаем, что правильный способ связать несколько итераторов/генераторов в один длинный один:

>>> list(itertools.chain(range(3), range(3))) 
[0, 1, 2, 0, 1, 2] 

но как я связать несколько файлов вместе и сохранить менеджеров контекста?

with open("auth.log", "rb") as f0: 
    with open("auth.log.1", "rb") as f1: 
     for line in itertools.chain(f0, f1): 
      do_stuff_with(line) 

    # f1 is now closed 
# f0 is now closed 
# gross 

я мог игнорировать менеджеров контекста и сделать что-то вроде этого, но он не чувствует себя хорошо:

files = itertools.chain(*(open(f, "rb") for f in file_names)) 
for line in files: 
    do_stuff_with(line) 

Или это вроде того, что Async IO - PEP 3156 для и я буду только ждать элегантный синтаксис позже?

+3

Также обратите внимание, что 'files = itertools.chain (* (open (f," rb ") для f в file_names))' в этом контексте определенно нехорошо. распаковка кортежа приводит к тому, что все ваши файлы будут открыты до того, как вы действительно войдете в конструктор 'chain'. Вам лучше с 'itertools.chain.from_iterable (open (fname, 'r') для fname в именах файлов))' - На самом деле это классическая причина, по которой метод класса 'from_iterable' должен существовать в первом место :). – mgilson

+0

@mgilson понятия не имел, 'from_iterable' был чем-то! Я рад, что мой usecase - пример учебника, почему он полезен. Я пытался выяснить, как правильно заставить ленивую оценку работать без вложенных циклов. Благодаря! –

+0

Обратите внимание, что даже 'from_iterable' не гарантирует, что все ваши файлы будут закрыты, когда вы закончите итерацию по нему, потому что вы никогда не знаете, когда будет запущен' __del__' (хотя я уверен, что они будут в Cpython) ... – mgilson

ответ

20

Всегда есть fileinput.

for line in fileinput.input(filenames): 
    ... 

source Чтение однако, оказывается, что fileinput.FileInput не может быть использован в качестве контекста менеджера . Чтобы исправить это, вы могли бы использовать contextlib.closing начиная FileInput экземпляры имеют более здраво реализован close метода:

from contextlib import closing 
with closing(fileinput.input(filenames)) as line_iter: 
    for line in line_iter: 
     ... 

Альтернативное с менеджером контекста, это написать простую функцию зацикливания над файлами и плодоношением линии, как вы перейти:

def fileinput(files): 
    for f in files: 
     with open(f,'r') as fin: 
      for line in fin: 
       yield line 

Нет реальной необходимости itertools.chain здесь ИМХО ... магия здесь в yield заявлении, которое используется для преобразования обычной функции Int o фантастически ленивый генератор.


Как и в сторону, начиная с python3.2, fileinput.FileInputявляется реализуется как менеджер контекста, который делает именно то, что мы делали раньше с contextlib. Теперь наш пример:

# Python 3.2+ version 
with fileinput.input(filenames) as line_iter: 
    for line in line_iter: 
     ... 

, хотя другой пример будет работать и на python3.2 +.

+2

+1, я никогда не знал о 'fileinput'. – Blender

+0

@Blender - это достойный модуль, который не слишком сильно используется, поскольку его функциональность может быть заменена на «chain.from_iterable». 'itertools' и' collections' являются более известными инструментами, которые люди достигают в 90% случаев. Я немного разочарован тем, что он не реализован как менеджер контекста, хотя (это даже не класс нового стиля). Похоже, это было бы довольно простое дополнение, но, к счастью, достаточно просто обернуть контекстным блоком. – mgilson

+3

Начиная с Python 3.2, 'fileinput' может использоваться как менеджер контекста (http://docs.python.org/3/library/fileinput.html. –

Смежные вопросы