Unter Linux gibt es einen Befehl namens "tail", mit dem Sie die "n" -Zeile am Ende der Datei abrufen können. Es ist ziemlich praktisch, daher möchte ich dasselbe mit Python tun können.
Ich möchte eine Funktion erstellen, die n Zeilen vom Ende der Datei mit tail (Dateiname, n)
abruft, wobei verschiedene Ansätze verwendet werden.
Den letzten Ansatz finden Sie auf der Website it-swarm.dev Suchen Sie effizient die letzte Zeile der Textdatei. -Ich verweise auf die Seite swarm.dev/ja/python/ Finde effizient die letzte Zeile der Textdatei / 940298444 /).
Die zu lesende Datei könnte eine Textdatei sein, aber dieses Mal werde ich die CSV-Datei verwenden. Der Dateiname lautet "test.csv". Der Inhalt ist eine Zusammenfassung des Bitcoin-Preises in 86400 Zeilen (im Wert von einem Tag) pro Sekunde.
test.csv
date,price,size
1588258800,933239.0,3.91528007
1588258801,933103.0,3.91169431
1588258802,932838.0,2.91
1588258803,933217.0,0.5089811
(Unterlassung)
1588345195,955028.0,0.0
1588345196,954959.0,0.05553
1588345197,954984.0,1.85356
1588345198,955389.0,10.91445135
1588345199,955224.0,3.61106
Obwohl es nichts mit dem Hauptthema zu tun hat, sind die Einheiten für Datum, Preis und Größe UnixTime, YEN, BTC, wenn Sie jeden Artikel vorerst erläutern. Die erste Zeile bedeutet, dass zum Zeitpunkt "1588258800", dh am 1. Mai um 0:00:00 Uhr, "3.915280007" Stückchen Münzen für "933239.0" Yen gekauft und verkauft wurden.
Verwenden Sie zunächst die integrierte Funktion open ()
, um das Dateiobjekt abzurufen, alle Zeilen von Anfang an zu lesen und nur die letzten n Zeilen auszugeben.
Wenn n 0 oder eine negative ganze Zahl ist, werden seltsame Ergebnisse erhalten, so dass es tatsächlich notwendig ist, eine auf natürliche Zahlen beschränkte Verarbeitung durchzuführen, dies ist jedoch für die Sichtbarkeit wichtig.
python
def tail(fn, n):
#Öffnen Sie die Datei und erhalten Sie alle Zeilen in einer Liste
with open(fn, 'r') as f:
#Lesen Sie eine Zeile.Die erste Zeile ist die Kopfzeile. Verwerfen Sie daher das Ergebnis
f.readline()
#Lesen Sie alle Zeilen
lines = f.readlines()
#Gibt nur n Zeilen von hinten zurück
return lines[-n:]
#Ergebnis
file_name = 'test.csv'
tail(file_name, 3)
# ['1588345197,954984.0,1.85356\n',
# '1588345198,955389.0,10.91445135\n',
# '1588345199,955224.0,3.61106\n']
Wenn es sich um eine Textdatei handelt, können Sie sie unverändert lassen, die Verwendung für CSV-Dateien jedoch etwas vereinfachen.
python
def tail(fn, n):
#Öffnen Sie die Datei und erhalten Sie alle Zeilen in einer Liste
with open(fn, 'r') as f:
f.readline()
lines = f.readlines()
#Geben Sie einen String zurück, nachdem Sie ihn zu einem Array gemacht haben.Übrigens str->Geben Sie die Konvertierung in float ein
return [list(map(float ,line.strip().split(','))) for line in lines[-n:]]
#Ergebnis
tail(file_name, 3)
# [[1588345197.0, 954984.0, 1.85356],
# [1588345198.0, 955389.0, 10.91445135],
# [1588345199.0, 955224.0, 3.61106]]
Das einzige, was sich geändert hat, ist die "Return" -Linie, aber da die Funktionen überfüllt und schwer zu verstehen sind, werde ich es gleich erklären. Die folgende Verarbeitung wird für jede Zeile durchgeführt.
strip ()
'1588345197,954984.0,1.85356\n'
-> '1588345197,954984.0,1.85356'
'1588345197,954984.0,1.85356'
-> ['1588345197', '954984.0', '1.85356']
map ()
von einem String in einen Float-Typ
['1588345197', '954984.0', '1.85356']
-> [1588345197.0, 954984.0, 1.85356]
Da das CSV-Modul jede Zeile automatisch in ein Array konvertiert, ist es etwas langsamer, kann aber genauer beschrieben werden.
python
import csv
def tail_csv(fn, n):
with open(fn) as f:
#Konvertieren Sie das Dateiobjekt in einen CSV-Reader
reader = csv.reader(f)
#Verwerfen Sie den Header
next(reader)
#Lesen Sie alle Zeilen
rows = [row for row in reader]
#Schweben Sie nur die letzten n Zeilen und kehren Sie zurück
return [list(map(float, row)) for row in rows[-n:]]
Da Pandas eine Schwanzfunktion haben, ist es überraschend einfach zu schreiben.
python
import pandas as pd
def tail_pd(fn, n):
df = pd.read_csv(fn)
return df.tail(n).values.tolist()
Da Pandas sich mit Numpy-Arrays befasst, wird es am Ende mit tolist ()
in eine Liste konvertiert. Es ist nicht erforderlich, ob das Numpy-Array verwendet werden kann.
Da ipython
einen praktischen Befehl namens timeit
hat, vergleichen wir ihn mit der Anzahl der Schleifen, die auf 100 gesetzt sind.
timeit -n100 tail('test.csv', 3)
18.8 ms ± 175 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
timeit -n100 tail_csv('test.csv', 3)
67 ms ± 822 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
timeit -n100 tail_pd('test.csv', 3)
30.4 ms ± 2.45 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
Es stellte sich heraus, dass es schnell zu lesen war, ohne ein Modul zu verwenden. Cospa scheint das Beste zu sein, weil Pandas die Einfachheit des Codes ist und die Geschwindigkeit angemessen ist. Da das CSV-Modul absichtlich auch in nicht verwendete Zeilen von einer Zeichenfolge in ein Array konvertiert, sind die Ergebnisse äußerst schlecht.
Alle bisherigen Ansätze lesen schließlich alle Zeilen. Da ich jedoch nur die letzten Zeilen möchte, sollte das Lesen sofort abgeschlossen sein, wenn es eine Möglichkeit gibt, die Datei von hinten zu lesen.
Siehe Seite [Die letzte Zeile der Textdatei effizient finden](https://www.it-swarm.dev/ja/python/ Die letzte Zeile der Textdatei effizient finden / 940298444 /) tat.
Lesen Sie ungefähr 100 Bytes von hinten in der Reihenfolge, und wenn ein Zeilenvorschubcode gefunden wird, ist die Zeichenfolge danach die letzte Zeile. Nur die letzte Zeile wird auf der Seite gefunden, aber um den Befehl "tail" zu realisieren Sie müssen die n
-Linie von hinten finden, stellen Sie sie also nur dort ein.
Als vorläufiges Wissen werden wir zunächst erklären, wie der Dateizeiger bedient wird.
Es gibt drei Funktionen: f.tell ()
, f.read (Größe)
und f.seek (Offset, woher)
.
f.tell ()
gibt die Position zurück, auf die der Zeiger aktuell zeigt.
f.read (size)
gibt den gelesenen Inhalt size
Bytes von der aktuellen Position zurück. Der Zeiger bewegt sich zur Leseposition. Er kann nur in die positive Richtung vorgerückt werden.
f.seek (offset, wherece)
ist eine Funktion, die die Position des Zeigers verschiebt.
Das Argument "woher" stellt die Position dar. Einer der Werte "0, 1, 2" wird eingegeben. "0" ist der Anfang der Datei, "1" ist die aktuelle Zeigerposition und "2" ist das Ende der Datei. Meint.
Geben Sie eine Ganzzahl für "Offset" ein. Im Gegensatz zu "Lesen" können Sie einen negativen Wert übergeben. So gibt beispielsweise "f.seek (-15, 1)" die aktuelle Zeigerposition um 15 an den Anfang zurück.
Wir werden es basierend auf diesen implementieren.
python
#Verwenden Sie split, das reguläre Ausdrücke verwenden kann
import re
def tail_b(fn, n=None):
#Wenn n nicht angegeben wird, wird nur die letzte Zeile alleine zurückgegeben
if n is None:
n = 1
is_list = False
#n ist eine natürliche Zahl
elif type(n) != int or n < 1:
raise ValueError('n has to be a positive integer')
#Wenn n angegeben ist, werden n Zeilen zusammen in einer Liste zurückgegeben.
else:
is_list = True
# 128 *Lesen Sie jeweils n Bytes
chunk_size = 64 * n
# seek()Benimmt sich unerwartet, außer im Binärmodus'rb'Konkretisieren
with open(fn, 'rb') as f:
#Erste Zeile, um die Position ganz links ohne die Überschrift zu finden(Kopfzeile)Ich lese
f.readline()
#Der allererste Zeilenvorschubcode befindet sich am linken Ende(Beenden Sie beim Lesen am Ende der Datei)Zu
# -1 ist'\n'1 Byte
left_end = f.tell() - 1
#Ende der Datei(2)1 Byte zurück von. read(1)Einlesen
f.seek(-1, 2)
#Am Ende der Datei befinden sich häufig Leerzeilen und Leerzeichen
#Position des letzten Zeichens in der Datei ohne diese(Rechtes Ende)Finden
while True:
if f.read(1).strip() != b'':
#Rechtes Ende
right_end = f.tell()
break
#Machen Sie einen Schritt, also machen Sie zwei Schritte nach unten
f.seek(-2, 1)
#Anzahl der ungelesenen Bytes links
unread = right_end - left_end
#Anzahl der gelesenen Zeilen.Wenn dies n oder mehr wird, bedeutet dies, dass n Zeilen gelesen wurden.
num_lines = 0
#Variablen zum Verbinden der gelesenen Byte-Strings
line = b''
while True:
#Die Anzahl der ungelesenen Bytes ist Chunk_Wenn es kleiner als die Größe wird,Chunk-Fraktion_Größe
if unread < chunk_size:
chunk_size = f.tell() - left_end
#Chunk von Ihrem aktuellen Standort_Bewegen Sie sich nach Größe an den Anfang der Datei
f.seek(-chunk_size, 1)
#Lesen Sie nur so viel, wie Sie sich bewegen
chunk = f.read(chunk_size)
#Verbinden
line = chunk + line
#Da ich wieder mit dem Lesen fortfuhr, Chunk am Anfang nochmal_Größenverschiebung
f.seek(-chunk_size, 1)
#Aktualisieren Sie die Anzahl der ungelesenen Bytes
unread -= chunk_size
#Wenn ein Zeilenvorschubcode enthalten ist
if b'\n' in chunk:
#Anzahl so viele wie die Anzahl der Zeilenvorschubcodes_Zeilen hochzählen
num_lines += chunk.count(b'\n')
#Lesen Sie mehr als n Zeilen,Oder wenn die Anzahl der ungelesenen Bytes 0 erreicht, endet ein Signal
if num_lines >= n or not unread:
#Zuletzt gefundener Zeilenvorschubcode
leftmost_blank = re.search(rb'\r?\n', line)
#Das Teil muss vor dem zuletzt gefundenen Zeilenvorschubcode nicht benötigt werden
line = line[leftmost_blank.end():]
#Konvertieren Sie die Byte-Zeichenfolge in eine Zeichenfolge
line = line.decode()
#'\r\n'Oder\n'In ein Array konvertieren, das durch getrennt ist
lines = re.split(r'\r?\n', line)
#Zum Schluss n Stücke von hinten herausnehmen,In Float-Typ konvertieren und zurückgeben
result = [list(map(float, line.split(','))) for line in lines[-n:]]
#Wenn n nicht angegeben wird, wird die letzte Zeile alleine zurückgegeben.
if not is_list:
return result[-1]
else:
return result
Die Erklärung finden Sie im Anhang. Lassen Sie uns die Hauptzeitmessung durchführen.
timeit -n100 tail_b(fn, 3)
87.8 µs ± 3.74 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Die bisher beste Zeit war der erste Ansatz, der "18,8 ms ± 175 µs" betrug. Dies bedeutet, dass die Ausführungszeit etwa "0,5%" beträgt. Das heißt "200" Mal, aber 86400 Zeilen von Anfang an. Es ist natürlich, dass es einen großen Unterschied gibt, weil es der Unterschied zwischen dem Lesen aller oder dem Lesen einiger Zeilen von hinten ist.
Ich habe vier Muster eingeführt, aber es scheint eine andere Möglichkeit zu geben, den Befehl "tail" des Systems mit dem Modul "subprocess" auszuführen. Dies ist eine umgebungsabhängige Methode, daher habe ich sie diesmal weggelassen. Die am meisten empfohlene Methode, die ich eingeführt habe, ist die, die mit "Pandas" in zwei Zeilen geschrieben werden kann. Python ist eine Sprache, die beherrscht, wie Sie sich mit dem Code eines anderen amüsieren können.
Die Methode zum Lesen von der Rückseite der Datei wird empfohlen, wenn Sie Geschwindigkeit benötigen oder wenn die Anzahl der Zeilen und Zeichen lächerlich groß ist und das Lesen der Datei von Anfang an zu lange dauert. Es macht auch keinen Sinn, "64" zu verwenden, um "chunk_size" zu bestimmen. Es ist wahrscheinlich am schnellsten, es auf ungefähr die Länge einer Zeile in einer Datei einzustellen, aber einige Dateien variieren je nach Zeile stark in der Länge. Daher kann ich nichts sagen. Wenn Sie mit einer Datei arbeiten, die einige Zeichen in einer kurzen Zeile, aber 10.000 Zeichen in einer langen Zeile enthält, müssen Sie chunk_size dynamisch ändern. Wenn beispielsweise die Anzahl der in einer Suche gefundenen Zeilen weit unter n liegt, wird die nächste chunk_size verdoppelt und verdoppelt. Es scheint auch effektiv zu sein, die nächste chunk_size aus der Anzahl der durchsuchten Zeilen und der durchschnittlichen Länge der Zeilen zu bestimmen.
Recommended Posts