Как мне перезапустить или заново сопоставить весь файл, не считывая все это в память?

Я хочу иметь возможность запускать регулярные выражения для всего файла, но мне бы хотелось, чтобы мне не приходилось считывать весь файл сразу в память, поскольку в будущем я могу работать с довольно большими файлами. Есть ли способ сделать это? Спасибо!

Уточнение: я не могу читать построчно, потому что он может занимать несколько строк.

8 ответов

Решение

Вы можете использовать mmap для отображения файла в память. Затем к содержимому файла можно получить доступ как к обычной строке:

import re, mmap

with open('/var/log/error.log', 'r+') as f:
  data = mmap.mmap(f.fileno(), 0)
  mo = re.search('error: (.*)', data)
  if mo:
    print "found error", mo.group(1)

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

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

Скажем, например, что это ваш файл:

Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Ut fringilla pede blandit
eros sagittis viverra. Curabitur facilisis
urna ABC elementum lacus molestie aliquet.
Vestibulum lobortis semper risus. Etiam
sollicitudin. Vivamus posuere mauris eu
nulla. Nunc nisi. Curabitur fringilla fringilla
elit. Nullam feugiat, metus et suscipit
fermentum, mauris ipsum blandit purus,
non vehicula purus felis sit amet tortor.
Vestibulum odio. Mauris dapibus ultricies
metus. Cras XYZ eu lectus. Cras elit turpis,
ultrices nec, commodo eu, sodales non, erat.
Quisque accumsan, nunc nec porttitor vulputate,
erat dolor suscipit quam, a tristique justo
turpis at erat.

И это было ваше регулярное выражение:

consectetur(?=\sadipiscing)

Теперь это регулярное выражение использует положительный прогноз и будет соответствовать строке "consectetur", только если за ним сразу следует какой-либо символ пробела, а затем строка "adipiscing".

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

Полагаю, неудачный ответ заключается в том, что все зависит от вашей ситуации.

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

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

Вот два места, которые описывают алгоритм для преобразования регулярного выражения в FSM:

Это один из способов:

import re

REGEX = '\d+'

with open('/tmp/workfile', 'r') as f:
      for line in f:
          print re.match(REGEX,line)
  1. с оператором в Python 2.5 принимает автоматическое закрытие файла. Следовательно, вам не нужно беспокоиться об этом.
  2. Итератор по объекту файла эффективен с точки зрения памяти. то есть он не будет читать больше строки памяти в данный момент времени.
  3. Но недостатком этого подхода является то, что для больших файлов потребуется много времени.

Другой подход, который приходит мне в голову, заключается в использовании методов read(size) и file.seek(offset), которые будут одновременно считывать часть размера файла.

import re

REGEX = '\d+'

with open('/tmp/workfile', 'r') as f:
      filesize = f.size()
      part = filesize / 10 # a suitable size that you can determine ahead or in the prog.
      position = 0 
      while position <= filesize: 
          content = f.read(part)
          print re.match(REGEX,content)
          position = position + part
          f.seek(position)

Вы также можете объединить эти два, там вы можете создать генератор, который будет возвращать содержимое определенных байтов в то время и перебирать этот контент для проверки вашего регулярного выражения. Это ИМО было бы хорошим подходом.

Вот вариант для вас, используя re и mmap, чтобы найти все слова в файле, который не создает списки, или загрузить весь файл в память.

import re
from contextlib import closing
from mmap import mmap, ACCESS_READ

with open('filepath.txt', 'r') as f:
    with closing(mmap(f.fileno(), 0, access=ACCESS_READ)) as d:
        print(sum(1 for _ in re.finditer(b'\w+', d)))

на основе ответа @sth, но с меньшим использованием памяти

Откройте файл и выполните итерации по строкам.

fd = open('myfile')
for line in fd:
    if re.match(...,line)
        print line

Python 3: чтобы загрузить файл как одну большую строку, используйте методы read() и decode()

import re, mmap


def read_search_in_file(file):
    with open('/var/log/error.log', 'r+') as f:
        data = mmap.mmap(f.fileno(), 0).read().decode("utf-8")
        error = re.search(r'error: (.*)', data)
  if error:
    return error.group(1)
f = open(filename,'r')
  for eachline in f:
    string=re.search("(<tr align=\"right\"><td>)([0-9]*)(</td><td>)([a-zA-Z]*)(</td><td>)([a-zA-Z]*)(</td>)",eachline)
    if string:
      for i in range (2,8,2):
        add = string.group(i)
        l.append(add)

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

Другие вопросы по тегам