[python] 파일을 역순으로 읽는 방법은 무엇입니까?

파이썬을 사용하여 파일을 역순으로 읽는 방법은 무엇입니까? 마지막 줄에서 첫 번째 줄까지 파일을 읽고 싶습니다.



답변

for line in reversed(open("filename").readlines()):
    print line.rstrip()

그리고 파이썬 3에서 :

for line in reversed(list(open("filename"))):
    print(line.rstrip())


답변

발전기로 작성된 정확하고 효율적인 답변.

import os

def reverse_readline(filename, buf_size=8192):
    """A generator that returns the lines of a file in reverse order"""
    with open(filename) as fh:
        segment = None
        offset = 0
        fh.seek(0, os.SEEK_END)
        file_size = remaining_size = fh.tell()
        while remaining_size > 0:
            offset = min(file_size, offset + buf_size)
            fh.seek(file_size - offset)
            buffer = fh.read(min(remaining_size, buf_size))
            remaining_size -= buf_size
            lines = buffer.split('\n')
            # The first line of the buffer is probably not a complete line so
            # we'll save it and append it to the last line of the next buffer
            # we read
            if segment is not None:
                # If the previous chunk starts right from the beginning of line
                # do not concat the segment to the last line of new chunk.
                # Instead, yield the segment first 
                if buffer[-1] != '\n':
                    lines[-1] += segment
                else:
                    yield segment
            segment = lines[0]
            for index in range(len(lines) - 1, 0, -1):
                if lines[index]:
                    yield lines[index]
        # Don't yield None if the file was empty
        if segment is not None:
            yield segment


답변

이런 식으로 어떻습니까 :

import os


def readlines_reverse(filename):
    with open(filename) as qfile:
        qfile.seek(0, os.SEEK_END)
        position = qfile.tell()
        line = ''
        while position >= 0:
            qfile.seek(position)
            next_char = qfile.read(1)
            if next_char == "\n":
                yield line[::-1]
                line = ''
            else:
                line += next_char
            position -= 1
        yield line[::-1]


if __name__ == '__main__':
    for qline in readlines_reverse(raw_input()):
        print qline

파일은 문자 단위로 역순으로 읽히기 때문에 개별 행이 메모리에 맞는 한 매우 큰 파일에서도 작동합니다.


답변

python module을 사용할 수도 있습니다 file_read_backwards.

pip install file_read_backwards(v1.2.1)을 통해 파일을 설치 한 후 다음을 통해 전체 파일을 메모리 효율적인 방식으로 뒤로 읽을 수 있습니다.

#!/usr/bin/env python2.7

from file_read_backwards import FileReadBackwards

with FileReadBackwards("/path/to/file", encoding="utf-8") as frb:
    for l in frb:
         print l

“utf-8”, “latin-1″및 “ascii”인코딩을 지원합니다.

python3에 대한 지원도 가능합니다. 추가 문서는 http://file-read-backwards.readthedocs.io/en/latest/readme.html 에서 찾을 수 있습니다.


답변

for line in reversed(open("file").readlines()):
    print line.rstrip()

당신이 리눅스에 있다면, 당신은 tac명령 을 사용할 수 있습니다 .

$ tac file

당신은 ActiveState에서 찾을 수 2 개 조리법 여기여기에


답변

import re

def filerev(somefile, buffer=0x20000):
  somefile.seek(0, os.SEEK_END)
  size = somefile.tell()
  lines = ['']
  rem = size % buffer
  pos = max(0, (size // buffer - 1) * buffer)
  while pos >= 0:
    somefile.seek(pos, os.SEEK_SET)
    data = somefile.read(rem + buffer) + lines[0]
    rem = 0
    lines = re.findall('[^\n]*\n?', data)
    ix = len(lines) - 2
    while ix > 0:
      yield lines[ix]
      ix -= 1
    pos -= buffer
  else:
    yield lines[0]

with open(sys.argv[1], 'r') as f:
  for line in filerev(f):
    sys.stdout.write(line)


답변

메모리에 맞지 않는 대용량 파일 (드문 경우는 아님)의 경우 허용되는 답변이 작동하지 않습니다.

다른 사람들이 지적했듯이 @srohde 답변 은 좋아 보이지만 다음 문제가 있습니다.

  • 파일 객체를 전달하고 읽을 인코딩을 결정하도록 사용자에게 맡길 수있는 경우 파일 열기가 중복되어 보입니다.
  • 파일 객체를 허용하도록 리팩토링하더라도 모든 인코딩에서 작동하지는 않습니다. utf-8인코딩 및 ASCII 이외의 내용이 포함 된 파일을 선택할 수 있습니다

    й

    전달 buf_size동일 1하고 것

    UnicodeDecodeError: 'utf8' codec can't decode byte 0xb9 in position 0: invalid start byte

    물론 텍스트가 더 클 buf_size수 있지만 선택되어 위와 같이 혼란스러운 오류가 발생할 수 있습니다.

  • 맞춤 줄 구분 기호를 지정할 수 없습니다.
  • 줄 구분 기호를 유지하도록 선택할 수 없습니다.

따라서 이러한 모든 문제를 고려하여 별도의 기능을 작성했습니다.

  • 바이트 스트림과 작동하는
  • 두 번째는 텍스트 스트림과 함께 작동하고 기본 바이트 스트림을 첫 번째 스트림에 위임하고 결과 행을 디코딩합니다.

우선 다음 유틸리티 함수를 정의 해 봅시다 :

ceil_division천장으로 나누기위한 ( //바닥이 있는 표준 나누기와 는 달리 , 이 스레드 에서 더 많은 정보를 찾을 수 있습니다 )

def ceil_division(left_number, right_number):
    """
    Divides given numbers with ceiling.
    """
    return -(-left_number // right_number)

split 오른쪽 끝에서 주어진 구분 기호로 문자열을 분할하여 유지할 수 있습니다.

def split(string, separator, keep_separator):
    """
    Splits given string by given separator.
    """
    parts = string.split(separator)
    if keep_separator:
        *parts, last_part = parts
        parts = [part + separator for part in parts]
        if last_part:
            return parts + [last_part]
    return parts

read_batch_from_end 이진 스트림의 오른쪽 끝에서 배치를 읽으려면

def read_batch_from_end(byte_stream, size, end_position):
    """
    Reads batch from the end of given byte stream.
    """
    if end_position > size:
        offset = end_position - size
    else:
        offset = 0
        size = end_position
    byte_stream.seek(offset)
    return byte_stream.read(size)

그런 다음 바이트 스트림을 읽는 기능을 역순으로 정의 할 수 있습니다

import functools
import itertools
import os
from operator import methodcaller, sub


def reverse_binary_stream(byte_stream, batch_size=None,
                          lines_separator=None,
                          keep_lines_separator=True):
    if lines_separator is None:
        lines_separator = (b'\r', b'\n', b'\r\n')
        lines_splitter = methodcaller(str.splitlines.__name__,
                                      keep_lines_separator)
    else:
        lines_splitter = functools.partial(split,
                                           separator=lines_separator,
                                           keep_separator=keep_lines_separator)
    stream_size = byte_stream.seek(0, os.SEEK_END)
    if batch_size is None:
        batch_size = stream_size or 1
    batches_count = ceil_division(stream_size, batch_size)
    remaining_bytes_indicator = itertools.islice(
            itertools.accumulate(itertools.chain([stream_size],
                                                 itertools.repeat(batch_size)),
                                 sub),
            batches_count)
    try:
        remaining_bytes_count = next(remaining_bytes_indicator)
    except StopIteration:
        return

    def read_batch(position):
        result = read_batch_from_end(byte_stream,
                                     size=batch_size,
                                     end_position=position)
        while result.startswith(lines_separator):
            try:
                position = next(remaining_bytes_indicator)
            except StopIteration:
                break
            result = (read_batch_from_end(byte_stream,
                                          size=batch_size,
                                          end_position=position)
                      + result)
        return result

    batch = read_batch(remaining_bytes_count)
    segment, *lines = lines_splitter(batch)
    yield from reverse(lines)
    for remaining_bytes_count in remaining_bytes_indicator:
        batch = read_batch(remaining_bytes_count)
        lines = lines_splitter(batch)
        if batch.endswith(lines_separator):
            yield segment
        else:
            lines[-1] += segment
        segment, *lines = lines
        yield from reverse(lines)
    yield segment

마지막으로 텍스트 파일을 되 돌리는 기능은 다음과 같이 정의 할 수 있습니다.

import codecs


def reverse_file(file, batch_size=None,
                 lines_separator=None,
                 keep_lines_separator=True):
    encoding = file.encoding
    if lines_separator is not None:
        lines_separator = lines_separator.encode(encoding)
    yield from map(functools.partial(codecs.decode,
                                     encoding=encoding),
                   reverse_binary_stream(
                           file.buffer,
                           batch_size=batch_size,
                           lines_separator=lines_separator,
                           keep_lines_separator=keep_lines_separator))

테스트

준비

fsutil명령을 사용하여 4 개의 파일을 생성했습니다 .

  1. 내용이없는 empty.txt , 크기 0MB
  2. 1MB 크기의 tiny.txt
  3. 크기가 10MB 인 small.txt
  4. 크기가 50MB 인 large.txt

또한 파일 경로 대신 파일 객체로 작업하기 위해 @srohde 솔루션을 리팩터링했습니다.

테스트 스크립트

from timeit import Timer

repeats_count = 7
number = 1
create_setup = ('from collections import deque\n'
                'from __main__ import reverse_file, reverse_readline\n'
                'file = open("{}")').format
srohde_solution = ('with file:\n'
                   '    deque(reverse_readline(file,\n'
                   '                           buf_size=8192),'
                   '          maxlen=0)')
azat_ibrakov_solution = ('with file:\n'
                         '    deque(reverse_file(file,\n'
                         '                       lines_separator="\\n",\n'
                         '                       keep_lines_separator=False,\n'
                         '                       batch_size=8192), maxlen=0)')
print('reversing empty file by "srohde"',
      min(Timer(srohde_solution,
                create_setup('empty.txt')).repeat(repeats_count, number)))
print('reversing empty file by "Azat Ibrakov"',
      min(Timer(azat_ibrakov_solution,
                create_setup('empty.txt')).repeat(repeats_count, number)))
print('reversing tiny file (1MB) by "srohde"',
      min(Timer(srohde_solution,
                create_setup('tiny.txt')).repeat(repeats_count, number)))
print('reversing tiny file (1MB) by "Azat Ibrakov"',
      min(Timer(azat_ibrakov_solution,
                create_setup('tiny.txt')).repeat(repeats_count, number)))
print('reversing small file (10MB) by "srohde"',
      min(Timer(srohde_solution,
                create_setup('small.txt')).repeat(repeats_count, number)))
print('reversing small file (10MB) by "Azat Ibrakov"',
      min(Timer(azat_ibrakov_solution,
                create_setup('small.txt')).repeat(repeats_count, number)))
print('reversing large file (50MB) by "srohde"',
      min(Timer(srohde_solution,
                create_setup('large.txt')).repeat(repeats_count, number)))
print('reversing large file (50MB) by "Azat Ibrakov"',
      min(Timer(azat_ibrakov_solution,
                create_setup('large.txt')).repeat(repeats_count, number)))

참고 : collections.deque클래스를 사용 하여 발전기를 배출했습니다.

출력

Windows 10의 PyPy 3.5의 경우 :

reversing empty file by "srohde" 8.31e-05
reversing empty file by "Azat Ibrakov" 0.00016090000000000028
reversing tiny file (1MB) by "srohde" 0.160081
reversing tiny file (1MB) by "Azat Ibrakov" 0.09594989999999998
reversing small file (10MB) by "srohde" 8.8891863
reversing small file (10MB) by "Azat Ibrakov" 5.323388100000001
reversing large file (50MB) by "srohde" 186.5338368
reversing large file (50MB) by "Azat Ibrakov" 99.07450229999998

Windows 10의 CPython 3.5의 경우 :

reversing empty file by "srohde" 3.600000000000001e-05
reversing empty file by "Azat Ibrakov" 4.519999999999958e-05
reversing tiny file (1MB) by "srohde" 0.01965560000000001
reversing tiny file (1MB) by "Azat Ibrakov" 0.019207699999999994
reversing small file (10MB) by "srohde" 3.1341862999999996
reversing small file (10MB) by "Azat Ibrakov" 3.0872588000000007
reversing large file (50MB) by "srohde" 82.01206720000002
reversing large file (50MB) by "Azat Ibrakov" 82.16775059999998

우리가 볼 수 있듯이 원래 솔루션처럼 작동하지만 더 일반적이며 위에 나열된 단점이 없습니다.


광고

나는 잘 테스트 된 기능 / 인용 유틸리티가 많은 패키지0.3.0 버전 ( Python 3.5 + 필요)에 이것을 추가했습니다 .lz

처럼 사용할 수 있습니다

 import io
 from lz.iterating import reverse
 ...
 with open('path/to/file') as file:
     for line in reverse(file, batch_size=io.DEFAULT_BUFFER_SIZE):
         print(line)

그것은 모든 표준 인코딩을 지원 합니다 ( utf-7내가 인코딩 할 수 있는 문자열을 생성 하기 위한 전략 을 정의하기가 어렵 기 때문에 제외 ).