Eine Antwort auf Geheiß von Kommentatoren zu meiner Antwort auf eine ähnliche Frage posten bei der dieselbe Technik verwendet wurde, um die letzte Zeile einer Datei zu mutieren, nicht nur um sie zu erhalten.
mmap
Dies ist für eine Datei von erheblicher Größe der beste Weg, dies zu tun. Um die vorhandene mmap
Antwort zu verbessern , ist diese Version zwischen Windows und Linux portierbar und sollte schneller ausgeführt werden (obwohl sie ohne einige Änderungen an 32-Bit-Python mit Dateien im GB-Bereich nicht funktioniert. Hinweise zur Handhabung finden Sie in der anderen Antwort und zum Ändern für 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)
Dies setzt voraus, dass die Anzahl der getailten Zeilen klein genug ist, damit Sie sie alle sicher gleichzeitig in den Speicher einlesen können. Sie können dies auch zu einer Generatorfunktion machen und jeweils eine Zeile manuell lesen, indem Sie die letzte Zeile durch Folgendes ersetzen:
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)
Zuletzt wird dies im Binärmodus gelesen (zur Verwendung erforderlich mmap
), sodass str
Zeilen (Py2) und bytes
Zeilen (Py3) erhalten werden. Wenn Sie unicode
(Py2) oder str
(Py3) möchten, kann der iterative Ansatz optimiert werden, um für Sie zu dekodieren und / oder Zeilenumbrüche zu korrigieren:
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
Hinweis: Ich habe dies alles auf einem Computer eingegeben, auf dem ich keinen Zugriff auf Python zum Testen habe. Bitte lassen Sie mich wissen, wenn ich etwas getippt habe. Dies war meiner anderen Antwort so ähnlich, dass ich denke, es sollte funktionieren, aber die Optimierungen (z. B. Handhabung eines offset
) könnten zu subtilen Fehlern führen. Bitte lassen Sie mich in den Kommentaren wissen, wenn es Fehler gibt.
seek(0,2)
danntell()
) zu erhalten, und verwende diesen Wert, um relativ zum Anfang zu suchen.