Đăng một câu trả lời theo lệnh của những người bình luận về câu trả lời của tôi cho một câu hỏi tương tự trong đó kỹ thuật tương tự đã được sử dụng để làm biến đổi dòng cuối cùng của một tệp, không chỉ nhận được nó.
Đối với một tập tin có kích thước đáng kể, mmap
là cách tốt nhất để làm điều này. Để cải thiện mmap
câu trả lời hiện có , phiên bản này có thể di động giữa Windows và Linux và sẽ chạy nhanh hơn (mặc dù nó sẽ không hoạt động nếu không có một số sửa đổi trên Python 32 bit với các tệp trong phạm vi GB, hãy xem câu trả lời khác để biết gợi ý về cách xử lý này và để sửa đổi để hoạt động trên 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)
Điều này giả định số lượng dòng đuôi là đủ nhỏ để bạn có thể đọc tất cả chúng vào bộ nhớ một cách an toàn cùng một lúc; bạn cũng có thể biến chức năng này thành một trình tạo và đọc thủ công một dòng tại một thời điểm bằng cách thay thế dòng cuối cùng bằng:
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)
Cuối cùng, điều này đọc ở chế độ nhị phân (cần thiết để sử dụng mmap
) để nó đưa ra str
các dòng (Py2) và bytes
dòng (Py3); nếu bạn muốn unicode
(Py2) hoặc str
(Py3), phương pháp lặp có thể được điều chỉnh để giải mã cho bạn và / hoặc sửa các dòng mới:
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
Lưu ý: Tôi đã gõ tất cả lên trên một máy mà tôi thiếu quyền truy cập vào Python để kiểm tra. Xin vui lòng cho tôi biết nếu tôi đánh máy bất cứ điều gì; điều này đủ tương tự với câu trả lời khác của tôi mà tôi nghĩ rằng nó nên hoạt động, nhưng các điều chỉnh (ví dụ xử lý một offset
) có thể dẫn đến các lỗi tinh vi. Xin vui lòng cho tôi biết trong các ý kiến nếu có bất kỳ sai lầm.
seek(0,2)
sau đótell()
) và sử dụng giá trị đó để tìm kiếm liên quan đến đầu.