Чтение строки из файла без продвижения [Pythonic Approach]
Какой питонический подход для чтения строки из файла, но не продвижения туда, где вы находитесь в файле?
Например, если у вас есть файл
cat1
cat2
cat3
и вы file.readline()
получите cat1\n
. Следующий file.readline()
вернет cat2\n
.
Есть ли какая-то функциональность, например file.some_function_here_nextline()
, чтобы получить cat1\n
, тогда вы можете сделать file.readline()
и вернуться cat1\n
?
Ответы
Ответ 1
Насколько я знаю, для этого нет встроенных функций, но такую функцию легко написать, поскольку большинство объектов Python file
поддерживают методы seek
и tell
для перехода в файл. Итак, процесс очень прост:
- Найдите текущую позицию в файле с помощью
tell
.
- Выполните операцию
read
(или write
).
-
seek
назад к предыдущему указателю файла.
Это позволяет вам делать приятные вещи, например читать фрагмент данных из файла, анализировать его и затем переписывать с помощью разных данных. Простая оболочка для функциональности может выглядеть так:
def peek_line(f):
pos = f.tell()
line = f.readline()
f.seek(pos)
return line
print peek_line(f) # cat1
print peek_line(f) # cat1
Вы можете реализовать то же самое для других методов read
так же легко. Например, реализуя одно и то же для file.read
:
def peek(f, length=1):
pos = f.tell()
data = f.read(length) # Might try/except this line, and finally: f.seek(pos)
f.seek(pos)
return data
print peek(f, 4) # cat1
print peek(f, 4) # cat1
Ответ 2
Вы можете использовать обернуть файл с помощью itertools.tee и вернуть два итератора с учетом предостережений, указанных в документации
Например
from itertools import tee
import contextlib
from StringIO import StringIO
s = '''\
cat1
cat2
cat3
'''
with contextlib.closing(StringIO(s)) as f:
handle1, handle2 = tee(f)
print next(handle1)
print next(handle2)
cat1
cat1
Ответ 3
Вручную делать это не так сложно:
f = open('file.txt')
line = f.readline()
print line
>>> cat1
# the calculation is: - (length of string + 1 because of the \n)
# the second parameter is needed to move from the actual position of the buffer
f.seek((len(line)+1)*-1, 1)
line = f.readline()
print line
>>> cat1
Вы можете обернуть это методом следующим образом:
def lookahead_line(file):
line = file.readline()
count = len(line) + 1
file.seek(-count, 1)
return file, line
И используйте его следующим образом:
f = open('file.txt')
f, line = lookahead_line(f)
print line
Надеюсь, это поможет!
Ответ 4
Библиотека more_itertools
предлагает класс peekable
, который позволяет вам peek()
вперед, не продвигая итерацию.
with open("file.txt", "r") as f:
p = mit.peekable(f.readlines())
p.peek()
# 'cat1\n'
next(p)
# 'cat1\n'
Мы можем перейти к следующей строке перед вызовом next()
для продвижения итерации p
. Теперь мы можем просмотреть следующую строку, снова вызвав peek()
.
p.peek()
# 'cat2\n'
См. также more_itertools
docs, так как peekable
позволяет вам prepend()
элементы переименовываться перед продвижением.
Ответ 5
Решения с tell()
/seek()
не будут работать с stdin
и другими итераторами. Более общая реализация может быть такой простой:
class lookahead_iterator(object):
__slots__ = ["_buffer", "_iterator", "_next"]
def __init__(self, iterable):
self._buffer = []
self._iterator = iter(iterable)
self._next = self._iterator.next
def __iter__(self):
return self
def _next_peeked(self):
v = self._buffer.pop(0)
if 0 == len(self._buffer):
self._next = self._iterator.next
return v
def next(self):
return self._next()
def peek(self):
v = next(self._iterator)
self._buffer.append(v)
self._next = self._next_peeked
return v
Использование:
with open("source.txt", "r") as lines:
lines = lookahead_iterator(lines)
magic = lines.peek()
if magic.startswith("#"):
return parse_bash(lines)
if magic.startswith("/*"):
return parse_c(lines)
if magic.startswith("//"):
return parse_cpp(lines)
raise ValueError("Unrecognized file")