Posting jawaban atas perintah komentator atas jawaban saya untuk pertanyaan serupa di mana teknik yang sama digunakan untuk mengubah baris terakhir file, tidak hanya mendapatkannya.
Untuk file dengan ukuran signifikan, mmap
adalah cara terbaik untuk melakukan ini. Untuk meningkatkan mmap
jawaban yang ada , versi ini portabel antara Windows dan Linux, dan harus berjalan lebih cepat (meskipun tidak akan berfungsi tanpa beberapa modifikasi pada 32 bit Python dengan file dalam rentang GB, lihat jawaban lain untuk petunjuk tentang penanganan ini , dan untuk memodifikasi agar berfungsi pada Python 2 ).
import io # Gets consistent version of open for both Py2.7 and Py3.x
import itertools
import mmap
def skip_back_lines(mm, numlines, startidx):
'''Factored out to simplify handling of n and offset'''
for _ in itertools.repeat(None, numlines):
startidx = mm.rfind(b'\n', 0, startidx)
if startidx < 0:
break
return startidx
def tail(f, n, offset=0):
# Reopen file in binary mode
with io.open(f.name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
# len(mm) - 1 handles files ending w/newline by getting the prior line
startofline = skip_back_lines(mm, offset, len(mm) - 1)
if startofline < 0:
return [] # Offset lines consumed whole file, nothing to return
# If using a generator function (yield-ing, see below),
# this should be a plain return, no empty list
endoflines = startofline + 1 # Slice end to omit offset lines
# Find start of lines to capture (add 1 to move from newline to beginning of following line)
startofline = skip_back_lines(mm, n, startofline) + 1
# Passing True to splitlines makes it return the list of lines without
# removing the trailing newline (if any), so list mimics f.readlines()
return mm[startofline:endoflines].splitlines(True)
# If Windows style \r\n newlines need to be normalized to \n, and input
# is ASCII compatible, can normalize newlines with:
# return mm[startofline:endoflines].replace(os.linesep.encode('ascii'), b'\n').splitlines(True)
Ini mengasumsikan jumlah garis berekor cukup kecil sehingga Anda dapat dengan aman membaca semuanya dalam memori sekaligus; Anda juga bisa menjadikan ini fungsi generator dan secara manual membaca satu baris sekaligus dengan mengganti baris terakhir dengan:
mm.seek(startofline)
# Call mm.readline n times, or until EOF, whichever comes first
# Python 3.2 and earlier:
for line in itertools.islice(iter(mm.readline, b''), n):
yield line
# 3.3+:
yield from itertools.islice(iter(mm.readline, b''), n)
Terakhir, ini dibaca dalam mode biner (perlu digunakan mmap
) sehingga memberikan str
garis (Py2) dan bytes
garis (Py3); jika Anda ingin unicode
(Py2) atau str
(Py3), pendekatan iteratif dapat diubah untuk memecahkan kode untuk Anda dan / atau memperbaiki baris baru:
lines = itertools.islice(iter(mm.readline, b''), n)
if f.encoding: # Decode if the passed file was opened with a specific encoding
lines = (line.decode(f.encoding) for line in lines)
if 'b' not in f.mode: # Fix line breaks if passed file opened in text mode
lines = (line.replace(os.linesep, '\n') for line in lines)
# Python 3.2 and earlier:
for line in lines:
yield line
# 3.3+:
yield from lines
Catatan: Saya mengetik semuanya ini di mesin tempat saya tidak memiliki akses untuk menguji Python. Tolong beri tahu saya jika saya salah mengetik; ini cukup mirip dengan jawaban saya yang lain sehingga saya pikir itu harus bekerja, tetapi tweak (misalnya menangani offset
) dapat menyebabkan kesalahan halus. Tolong beri tahu saya di komentar jika ada kesalahan.
seek(0,2)
lalutell()
), dan menggunakan nilai itu untuk mencari relatif ke awal.