การโพสต์คำตอบตามคำสั่งของผู้แสดงความคิดเห็นในคำตอบของคำถามที่คล้ายกันที่ซึ่งใช้เทคนิคเดียวกันในการแปลงบรรทัดสุดท้ายของไฟล์ไม่ใช่แค่เอามาใช้
สำหรับไฟล์ขนาดใหญ่mmap
เป็นวิธีที่ดีที่สุดในการทำเช่นนี้ เพื่อปรับปรุงmmap
คำตอบที่มีอยู่เวอร์ชันนี้เป็นแบบพกพาระหว่าง Windows และ Linux และควรทำงานได้เร็วขึ้น (แม้ว่ามันจะไม่ทำงานหากไม่มีการแก้ไขบางอย่างใน Python 32 บิตพร้อมไฟล์ในช่วง GB ดูคำตอบอื่น ๆ สำหรับคำแนะนำในการจัดการนี้ และสำหรับการดัดแปลงเพื่อทำงานบน 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)
นี่จะถือว่าจำนวนบรรทัดที่มีขนาดเล็กพอที่คุณสามารถอ่านได้อย่างปลอดภัยทั้งหมดในหน่วยความจำทันที คุณสามารถทำให้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้านี้และอ่านบรรทัดด้วยตนเองในเวลาเดียวกันโดยแทนที่บรรทัดสุดท้ายด้วย:
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)
สุดท้ายนี้การอ่านในโหมดไบนารี (จำเป็นต้องใช้mmap
) ดังนั้นมันจึงให้str
บรรทัด (Py2) และbytes
บรรทัด (Py3); หากคุณต้องการunicode
(Py2) หรือstr
(Py3) วิธีการวนซ้ำอาจถูกปรับแต่งเพื่อถอดรหัสให้คุณและ / หรือแก้ไขบรรทัดใหม่:
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
หมายเหตุ: ฉันพิมพ์สิ่งนี้ทั้งหมดบนเครื่องที่ฉันไม่สามารถเข้าถึง Python เพื่อทดสอบ กรุณาแจ้งให้เราทราบหากฉันพิมพ์อะไร; นี่คล้ายกับคำตอบอื่น ๆ ของฉันที่ฉันคิดว่าควรใช้งานได้ แต่การปรับแต่ง (เช่นการจัดการoffset
) อาจทำให้เกิดข้อผิดพลาดเล็กน้อย โปรดแจ้งให้เราทราบในความคิดเห็นหากมีข้อผิดพลาดใด ๆ
seek(0,2)
จากนั้นtell()
) และใช้ค่านั้นเพื่อค้นหาสัมพันธ์กับจุดเริ่มต้น