Подтвердить что ты не робот

Чтение строки из файла без продвижения [Pythonic Approach]

Какой питонический подход для чтения строки из файла, но не продвижения туда, где вы находитесь в файле?

Например, если у вас есть файл

cat1
cat2
cat3

и вы file.readline() получите cat1\n. Следующий file.readline() вернет cat2\n.

Есть ли какая-то функциональность, например file.some_function_here_nextline(), чтобы получить cat1\n, тогда вы можете сделать file.readline() и вернуться cat1\n?

4b9b3361

Ответ 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")