искать регулярное выражение в большом файле, используя python

Я пытаюсь найти токен «: путь» в файле, а затем прочитать все следующие (произвольное количество цифр) числа как число (поэтому для «: путь, 123» я ищу в файле, затем читаю целое число 123). Затем прочитайте символы между текущей позицией поиска и pos+123 (сохраните их в списке или где-то еще). Затем выполните поиск до следующего совпадения для «: path» и повторите процесс.

Я хотел бы, чтобы функция была похожа на:

def fregseek(FILE, current_seek, /regex/):

.
.
  value_found = ?  # result of reading next N chars after :path,[0-9]+
.
.
  return next_start_seek, value_found

Может быть любое количество совпадений для ':path' в строке, и эта строка может встречаться в пределах количества символов, указанного после ','. Я написал беспорядочную кучу мусора, которая читается в каждой строке, затем для каждой строки жует первые N символов, указанных совпадением, затем продолжает обрабатывать строку, пока она не будет съедена полностью. Затем читает следующую строку и так далее.

Это ужасно, я не хочу вычерпывать все строки из потенциально огромного файла, когда все, что мне действительно нужно сделать, это искать (тем более, что новая строка не имеет значения, поэтому требуется дополнительный шаг обработки только потому, что строки легко найти). вытягивать из файлов смешно).

Так вот, это моя проблема, которую я хотел бы решить. Мне нужно найти совпадение, прочитать значение, продолжить с конца этого значения в поисках следующего совпадения и так далее, пока файл не будет исчерпан.

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

Я хотел бы избегать нестандартных библиотек, если это возможно, я также хотел бы иметь самый короткий код, но это меньше всего меня беспокоит (скорость и потребление памяти являются важными факторами, но я не хочу 50 дополнительных лок только для загрузки некоторых библиотека с небольшой функциональностью, которую я мог бы просто выдрать, если бы только знал, что это такое).

Я бы предпочел код на python, однако, если perl превзойдет python в этом отношении, я буду использовать вместо него perl, я также открыт для умных сценариев sed/awk/bash и т. д., если они не будут ужасно медленнее.

Большое спасибо заранее.


person sillyMunky    schedule 26.09.2012    source источник
comment
Нужно ли использовать регулярное выражение? Если вы просто пытаетесь найти токен, например :path, в этом нет необходимости, и будет проще (и эффективнее), если вы просто выполните строковый поиск.   -  person abarnert    schedule 27.09.2012
comment
Кроме того, вы продолжаете говорить о поиске, но нет способа выполнить поиск без сканирования всех байтов, и я не вижу ничего, что вы не могли бы сделать за один проход, поэтому я не уверен, зачем вам нужен поиск вообще.   -  person abarnert    schedule 27.09.2012
comment
Спасибо за комментарии abarnert. Строковый поиск хорош, если мне не нужно сразу читать весь файл, но мне нужно эффективно обрабатывать любой фрагмент, который я читал. Я не уверен, что есть лучший способ, чем читать все это в , хотя я хотел бы иметь возможность обрабатывать произвольно большие файлы. В идеале у меня было бы несколько вариантов бенчмарка, но сейчас у меня просто мой дрянной код, уже есть ответ намного лучше того, что есть сейчас :)   -  person sillyMunky    schedule 27.09.2012
comment
Хорошо, можете ли вы сопоставить весь файл сразу (и позволить ОС беспокоиться о дисковом вводе-выводе)? Если вы не знаете ответа, вероятно, да, если ваши файлы имеют размер ‹‹2 ГБ или если вас интересуют только 64-разрядные платформы, никак иначе. Если ответ «да», вы можете написать код, почти такой же, как если бы вы читали весь файл в str/bytes, но вместо этого использовали объект mmap.   -  person abarnert    schedule 27.09.2012
comment
Обычно я могу отображать весь файл сразу, но иногда файл превышает 4 ГБ, и я хотел бы, если это возможно, не ограничивать себя 64-битными машинами. Я также не уверен, будут ли тесты альтернативных подходов (например, чтение по частям) работать лучше, но я бы хотел посмотреть, какие алгоритмы придумали люди. Не имея более универсального подхода, мне нравится тот, что представлен BrtH, я думаю, что это элегантное решение моей проблемы, даже если это не совсем то, о чем я просил.   -  person sillyMunky    schedule 27.09.2012
comment
Основное преимущество mmap перед f.read() заключается в том, что на 64-битном Python он работает с большими файлами. (Кроме того, это, вероятно, будет быстрее, но насколько быстрее, трудно предположить, и не стоит пытаться угадать, когда можно просто протестировать.) Но если вам нужны большие файлы на 32-битном Python, этого недостаточно. Для этого используйте скользящее окно Google mmap, и вы должны найти несколько рецептов, которые делают то, что вам нужно (но не будут такими же эффективными, как обычный mmap, если вы можете его использовать).   -  person abarnert    schedule 27.09.2012


Ответы (2)


Если вам не нужны регулярные выражения, вы можете сделать это, просто найдя и нарезав.

В любом случае тривиальное решение состоит в том, чтобы прочитать весь файл в память, найти и разрезать результирующий объект str/bytes.

Но это не работает, если вы не можете (или не хотите) читать весь файл в память.

К счастью, если вы можете рассчитывать на то, что размер ваших файлов составляет ‹‹ 2 ГБ или вам нужно работать только на 64-разрядном Python, и вы работаете на разумной платформе (POSIX, современная Windows и т. д.), вы можете mmap файл в память вместо этого. Объект mmap имеет подмножество тех же методов, что и строки, поэтому вы можете просто представить, что у вас есть строка, как если бы вы прочитали весь файл в память, но вы можете рассчитывать на реализацию Python и ОС, чтобы сделать это. просто работать с разумной эффективностью.

В зависимости от вашей версии Python re может быть не в состоянии сканировать mmap, как если бы это была строка, он может работать, но медленно, или может работать нормально. Итак, вы можете сначала попробовать это, и если он не выдает исключение или работает намного медленнее, чем вы ожидали, все готово:

def findpaths(fname):
  with open(fname, 'rb') as f:
    m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    for match in re.finditer(':path,([0-9]+)', m):
      yield m[match.end():match.end()+int(match.group(1))]

(Это то же самое, что и ответ BrtH, только с использованием mmap вместо строки и преобразованием в генератор вместо списка - хотя, конечно, вы могли бы сделать последнюю часть, просто заменив его квадратные скобки круглыми скобками.)

Если вы используете более старую (или не CPython?) версию Python, которая не может (эффективно) re и mmap, это немного сложнее:

def nextdigits(s, start):
  return ''.join(itertools.takewhile(str.isdigit,
                                     itertools.islice(s, start, None)))

def findpaths(fname):
  with open(fname, 'rb') as f:
    m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    i = 0
    while True:
      n = m.find(':path', i)
      if n == -1: return
      countstr = nextdigits(m, n+6)
      count = int(countstr)
      n += 6 + len(countstr)
      yield m[n:n+count]
      i = n + 6 + count

Вероятно, это не самый быстрый способ написать функцию nextdigits. Я не уверен, что это действительно будет иметь значение (рассчитайте время и посмотрите), но если это произойдет, есть другие возможности — вырезать m[n+6:n+A_BIG_ENOUGH_NUMBER] и использовать регулярное выражение, или написать собственный цикл, или… С другой стороны, если это ваше узкое место, вы можете получить гораздо больше пользы, переключившись на интерпретатор с JIT (PyPy, Jython или IronPython)…

Для своих тестов я разделил вещи: findpaths принимает строковый объект, а вызывающая программа выполняет биты with open и mmap и просто передает m в findpaths; Я не делал этого здесь только для краткости.

Во всяком случае, я проверил обе версии на следующих данных:

BLAH:path,3abcBLAH:path,10abcdefghijklmnBLAH:path,3abc:path,0:path,3abc

И выход был:

abc
abcdefghij
abc

abc

Я думаю, это правильно?

Если бы моя более ранняя версия заставляла его вращаться на 100% ЦП, я бы предположил, что я неправильно увеличил i в цикле; это наиболее распространенная причина, по которой вы получаете такое поведение в узком цикле синтаксического анализа. В любом случае, если вы можете воспроизвести это с текущей версией, пожалуйста, опубликуйте данные.

person abarnert    schedule 26.09.2012
comment
Спасибо за ваше предложение, мне нравится идея вернуть генератор. Это не совсем работает для меня, по какой-то причине, когда я на самом деле пытаюсь использовать возвращенный генератор, я либо получаю очень быстрое выполнение, при этом ничего не происходит, либо жуя все мои системные ресурсы и нуждаясь в убийстве (с очень маленьким тестовым файлом). Не могли бы вы показать мне, как вы его использовали, пожалуйста? - person sillyMunky; 27.09.2012
comment
Это хороший ответ, и он соответствует требованиям, вероятно, лучше, чем мой, поэтому +1. - person BrtH; 27.09.2012
comment
Но есть одна вещь, которую я не понимаю. Кажется, вы предполагаете, что счет уже известен и постоянен. Но если я правильно понял вопрос, то это не так, и нужно еще найти счет. И если счетчик всегда состоит из трех цифр, вам придется найти его с помощью регулярного выражения. И я думаю, что вы можете использовать i = n + 7, потому что слово :path,{at least one digit} не может пересекаться. - person BrtH; 27.09.2012
comment
@BrtH: правильно в обоих случаях. Вам не обязательно использовать регулярное выражение для считывания счетчика, но это определенно проще и, возможно, эффективнее. Даже если вы не можете использовать регулярное выражение mmap, возможно, лучшим решением будет регулярное выражение небольшого фрагмента, такого как m[n+6:n+50]. - person abarnert; 27.09.2012
comment
Большое спасибо за ваш ответ, очень исчерпывающий и хорошо объясненный. Теперь я могу эффективно обрабатывать потоковые файлы mitmproxy (что само по себе является незначительной частью моего текущего проекта). - person sillyMunky; 29.09.2012
comment
Красиво расширено, приятно видеть, как вы объединили ответы. Я не знал о таких вещах, как mmap, прежде чем ответить на этот вопрос, поэтому спасибо, что научили меня кое-чему о производительности и прочем. - person BrtH; 30.09.2012

Вы можете сделать это почти одной строкой в ​​python:

with open('filename.txt') as f:
    text = f.read()

results = [text[i[0]:i[0] + i[1]] for i in 
           ((m.end(), int(m.group(1))) for m in
            re.finditer(':path,([0-9]+)', text))]

Примечание: не проверено...

person BrtH    schedule 26.09.2012
comment
У меня это отлично работает для небольших файлов, большое спасибо! Я проголосовал, потому что это хороший ответ и гораздо более эффективный, чем то, что у меня было. Я жду ответа, который не требует чтения всего файла сразу, но обрабатывает произвольно большие файлы (возможно, с использованием mmap?). Если я не найду его здесь, я приму ваш, потому что он помог мне перейти к гораздо более широкому кругу файлов, хотя и не соответствует зениту моих требований (произвольный размер файла с небольшими дополнительными затратами ... немало приказ!). Еще раз спасибо за ваш вклад :) - person sillyMunky; 27.09.2012