Я хочу выполнить базовую фильтрацию файла. Прочтите, обработайте, напишите обратно.
Я не ищу «игры в гольф», мне нужен самый простой и элегантный способ добиться этого. Я придумал:
from __future__ import with_statement
filename = "..." # or sys.argv...
with open(filename) as f:
new_txt = # ...some translation of f.read()
open(filename, 'w').write(new_txt)
Оператор with
делает работу короче, поскольку мне не нужно явно открывать и закрывать файл.
Есть другие идеи?
Кажется, это работает:
with open(filename, "r+") as f:
new_txt = process(f.read())
f.truncate(0)
f.write(new_txt)
Для элегантности я бы пошел другим путем: реализуйте ваши операции чтения и фильтрации файлов в качестве генераторов. Вы напишете больше строк кода, но это будет более гибкий, поддерживаемый и производительный код.
См. Генераторные хитрости для системных программистов Дэвида М. Бизли, который действительно важен для чтения всем, кто пишет такой код.
Отличная ссылка - спасибо! Меня немного беспокоит возросшая сложность отладки конвейеров, но мощность неоспорима.
Разработка через тестирование - ваш друг.
Если вы ищете python-эквивалент perl -pi, вот неплохой вариант:
import fileinput for line in fileinput.input(): # process line
Подробнее см. http://www.python.org/doc/2.5.2/lib/module-fileinput.html.
Сделав это таким образом, вы должны использовать свой скрипт python в конвейере для создания нового файла:
$ myscript.py infile.txt > outfile.txt
Хотя это мне не очень помогает, так как я хочу записать обратно в тот же файл. И перенаправление не будет работать таким образом для одного и того же файла
Чтобы сделать это так, чтобы не съесть свои данные, если вы рухнете посередине:
from twisted.python.filepath import FilePath
p = FilePath(filename)
p.setContent(process(p.getContent()))
На самом деле более простой способ использования fileinput - использовать параметр inplace:
import fileinput
for line in fileinput.input (filenameToProcess, inplace=1):
process (line)
Если вы используете параметр inplace, он перенаправит stdout в ваш файл, так что, если вы сделаете печать, он запишет обратно в ваш файл.
В этом примере в файл добавляются номера строк:
import fileinput
for line in fileinput.input ("b.txt",inplace=1):
print "%d: %s" % (fileinput.lineno(),line),
Очень хорошо, спасибо, что указали на этот вариант. Вы также можете использовать функцию filelineno () из fileinput, чтобы автоматически получать номер строки, не считая его самостоятельно.
Да, и вы забыли запятую после печати - код добавляет лишние символы новой строки :-)
Спасибо, что уловили это - я изменил пример.
Мое уродливое (но короткое, как указано в вопросе) решение с генератор выражений;
# Some setup first
file('test.txt', 'w').write('\n'.join('%05d' % i for i in range(100)))
# This is the filter function
def f(i):
return i % 3
# This is the main part
file('test2.txt', 'w').write('\n'.join(str(f(int(l))) for l in file('test.txt', 'r').readlines()))
# And a wrapper for sanity
def filter_file(infile, outfile, filter_function)
outfile.write('\n'.join(filter_function(l) for l in infile.readlines()))
Работает здесь только при вызове
f.seek(0)
послеf.truncate(0)
, в противном случае новый файл начинается с 11 нулевых байтов (Python 2.7.3 в Linux).