Publier une réponse à la demande des commentateurs sur ma réponse à une question similaire où la même technique a été utilisée pour muter la dernière ligne d'un fichier, pas seulement pour l'obtenir.
Pour un fichier de taille importante, mmap
c'est la meilleure façon de le faire. Pour améliorer la mmap
réponse existante , cette version est portable entre Windows et Linux, et devrait fonctionner plus rapidement (même si elle ne fonctionnera pas sans quelques modifications sur Python 32 bits avec des fichiers de la plage Go, voir l' autre réponse pour des conseils sur la gestion de cela , et pour modifier pour travailler sur 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)
Cela suppose que le nombre de lignes suivies est suffisamment petit pour pouvoir les lire toutes en mémoire en toute sécurité; vous pouvez également en faire une fonction de générateur et lire manuellement une ligne à la fois en remplaçant la dernière ligne par:
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)
Enfin, cette lecture en mode binaire (nécessaire à utiliser mmap
) donc elle donne des str
lignes (Py2) et des bytes
lignes (Py3); si vous voulez unicode
(Py2) ou str
(Py3), l'approche itérative pourrait être modifiée pour décoder pour vous et / ou corriger les nouvelles lignes:
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
Remarque: j'ai tapé tout cela sur une machine sur laquelle je n'ai pas accès à Python pour tester. S'il vous plaît laissez-moi savoir si j'ai fait une faute de frappe; c'était assez similaire à mon autre réponse que je pense que cela devrait fonctionner, mais les ajustements (par exemple la gestion d'un offset
) pourraient conduire à des erreurs subtiles. S'il vous plaît laissez-moi savoir dans les commentaires s'il y a des erreurs.
seek(0,2)
alorstell()
), et j'utilise cette valeur pour rechercher par rapport au début.