Редактирование файлов в Python - PullRequest
3 голосов
/ 09 марта 2009

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

Проблема в том, что я не заменяю на месте. Я открываю файлы, передаю содержимое в память, а затем перезаписываю файл следующим образом:

file = open('path/to/file','r')
in_string = file.read()
file.close()
# ...
#Processing logic
# ...
file = open('path/to/file','w')
file.write(out_string)
file.close()

Помимо очевидных проблем с производительностью и памятью, которые являются законными, но не настолько серьезными для моего использования, есть еще один недостаток этого метода. SVN волнуется. Я могу сделать некоторые обходные пути после факта, чтобы исправить ошибку контрольной суммы, которую svn генерирует при коммите, но это делает утилиту бессмысленной.

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

Ответы [ 6 ]

8 голосов
/ 09 марта 2009

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

Может быть, вы случайно редактируете файлы в каталоге .svn? В .svn/text-base Subversion хранит копии ваших файлов с тем же именем и расширением .svn-base, убедитесь, что вы их не редактируете!

2 голосов
/ 09 марта 2009

Что вы подразумеваете под "SVN freaks"?

В любом случае, vi / emacs / etc работает следующим образом:

f = open("/path/to/.file.tmp", "w")
f.write(out_string)
f.close()
os.rename("/path/to/.file.tmp", "/path/to/file")

(хорошо, там на самом деле есть "fsync" ... Но я не знаю, как это сделать в Python)

Причина, по которой он делает эту копию, состоит в том, чтобы гарантировать, что, если система умирает на полпути после записи нового файла, старый все еще там ... И операция 'переименования' определяется как атомарная, поэтому она будет либо работаете (вы получаете 100% нового файла), либо не работаете (вы получаете 100% старого файла) - у вас никогда не останется половина файла.

1 голос
/ 09 марта 2009

Возможно, модуль fileinput может сделать ваш код проще / короче:

Вот пример:

import fileinput

for line in fileinput.input("test.txt", inplace=1):
    print "%d: %s" % (fileinput.filelineno(), line),
0 голосов
/ 09 марта 2009

Я подозреваю Ответ Фердинанда , что вы возвращаетесь в .svn dir, объясняет, почему вы портите SVN, но учтите, что есть еще один недостаток в обработке файлов.

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

  1. Считайте файл в память и выполните перевод
  2. Записать новое содержимое в "filename.new", а не в исходное имя файла.
  3. Удалите исходный файл и переименуйте «filename.new» в «filename»

Таким образом, вы не рискуете потерять данные, если их убьют в неправильной точке. Обратите внимание, что модуль fileinput справится с большей частью этого за вас. Может быть задана последовательность файлов для обработки, и если вы укажете inplace = True, перенаправит стандартный вывод в соответствующий файл (сохраняя резервную копию). Затем вы можете структурировать свой код примерно так:

import os
import fileinput

def allfiles(dir, ignore_dirs=set(['.svn'])):
    """Generator yielding all writable filenames below dir.
    Ignores directories specified 
    """
    for basedir, dirs, files in os.walk(dir):
        if basedir in ignore_dirs:
            dirs[:]=[] # Don't recurse
            continue  # Skip this directory

        for filename in files:
            filename = os.path.join(basedir, filename)
            # Check the file is writable
            if os.access(filename, os.W_OK):
                yield filename


for line in fileinput.input(allfiles(PATH_TO_PROCESS), inplace=True):
    line = perform_some_substitution(line)
    print line.rstrip("\n") # Print adds a newline, but line already has one
0 голосов
/ 09 марта 2009

Попробуйте 'file = open (' path / to / file ',' w + ')'. Это означает, что вы обновляете существующий файл, а не пишете новый.

0 голосов
/ 09 марта 2009

Урод как? То, что вы описываете, если оно работает, означает редактирование файла "на месте", по крайней мере, столько же, сколько vi (1) делает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...