Hallo,
ich habe große Text-Dateien (>2000000 Zeilen), die ich umformatieren möchte. Dazu lese ich diese zeilenweise ein und verändere diese dann, soweit so gut.
Leider dauert die ganze Prozedur seehr lange.
Wie kann ich nun zwischendruch feststellen, wie viele Bytes denn schon verarbeitet wurden bzw. an welcher Stelle ich mich im File befinde? tell() funktioniert nicht, da ich zeilenweise über File iteriere und das sich irgendwie beißt.
Ich könnte natürlich byteweise einlesen, doch finde ich dies extrem unschön.
Hat jemand vielleicht noch Tipps für den Umgang mit sehr großen Dateien (mehrere GB)?
Danke schonmal
Position in einer Datei feststellen
@RedSharky: Du kannst doch einfach die Zeilen mitzählen oder die Länge der einzelnen Zeilen aufaddieren.
Hm, ich möchte aber doch die Zeilenanzahl relativ zur Gesamt-Zeilenzahl wissen. Die Gesamt-Zeilenzahl weiß ich doch aber erst am Ende, oder? Tatsächlich gebe ich mir alle zehn Sekunden die abgearbeitete Zeilenzahl aus. Jedoch bringt mir das nicht wirklich was, wenn nicht bekannt ist ob auf Zeile 204054 noch 2000000 oder 4000000 folgen.
Oder kann ich die Gesamt-Zeilenzahl schon ganz am Anfang ermitteln, etwa so wie die Dateigröße?
Oder kann ich die Gesamt-Zeilenzahl schon ganz am Anfang ermitteln, etwa so wie die Dateigröße?
Noch eine Ergänzende Frage.
Auf meinem recht aktuellen Computer werden nur ca. 220 Text-Zeilen pro Sekunde abgearbeitet. Ist das normal oder ist das doch recht mickrig? Immerhin habe ich Millionen von Zeilen umzuschreiben.
Auf meinem recht aktuellen Computer werden nur ca. 220 Text-Zeilen pro Sekunde abgearbeitet. Ist das normal oder ist das doch recht mickrig? Immerhin habe ich Millionen von Zeilen umzuschreiben.
@RedSharky: Wenn die Zeilen nicht alle gleich lang sind, kann man die Gesamtzeilenzahl nicht ermitteln ohne die Datei einmal komplett gelesen zu haben. Man kann mit `tell()` die aktuelle Position ermitteln. Warum geht das bei Dir nicht?
Über die Geschwindigkeit kann man nicht viel sagen solange man nicht weiss um wieviele Daten es sich handelt und was Du damit genau machst.
Wenn Du wissen willst wo in Deinem Programm die meiste Zeit verbracht wird, musst Du mit einem entsprechenden Profiling-Modul nachmessen.
Über die Geschwindigkeit kann man nicht viel sagen solange man nicht weiss um wieviele Daten es sich handelt und was Du damit genau machst.
Wenn Du wissen willst wo in Deinem Programm die meiste Zeit verbracht wird, musst Du mit einem entsprechenden Profiling-Modul nachmessen.
Wenn ich tell() einfüge, kommt die Fehlermeldung:
IOError: telling position disabled by next() call
Ich hab mal mein aktuelles Script rangehängt. Eigentlich soll es nur Dateien aus einer DNA-Sequenz-Datenbank einlesen und den Inhalt getrennt in zwei Dateien speichern. Wenn die Zeile mit "@" beginnt, kommt alles folgende in Datei1, bei "+" wird alles in Datei2 gespeichert.
Meine Fragen:
Wie kann ich die "Länge der Zeilen aufaddieren", sodass diese dem Speicherverbrauch in der Datei (bzw. der Position des Cursors) entspricht?
Warum geht tell() nicht, bzw. was mache ich falsch?
Warum ist der Code so lahm?
Wie messe ich am besten die Zeit der kritischen for-Schleife?
IOError: telling position disabled by next() call
Ich hab mal mein aktuelles Script rangehängt. Eigentlich soll es nur Dateien aus einer DNA-Sequenz-Datenbank einlesen und den Inhalt getrennt in zwei Dateien speichern. Wenn die Zeile mit "@" beginnt, kommt alles folgende in Datei1, bei "+" wird alles in Datei2 gespeichert.
Code: Alles auswählen
import time
infile = "seq.fastq"
fasta_file = "seq.fas"
qual_file = "seq.q"
########################################################################
def get_file(infile):
try:
f = open(infile, "r")
print("File opened...")
op1 = ""
op2 = ""
counter = 0
t1 = time.time()
for line in f:
print(f.tell())
counter += 1
t2 = time.time()
if (t2-t1) > 10:
print(time.asctime(time.localtime()), ":", counter, "lines processed")
t1 = t2
if line.startswith('@'):
op1 += line
dest = 1
elif line.startswith('+'):
op2 += line
dest = 2
else:
if dest == 1:
op1 += line
elif dest == 2:
op2 += line
finally:
f.close()
print("File closed.")
return op1, op2
def write_to_file(op_file, op):
try:
f = open(op_file, "w")
f.write(op)
finally:
f.close()
print("Data writen to:", op_file)
########################################################################
print("This script splits the data of fastq files into two seperate files.")
output1, output2 = get_file(infile)
write_to_file(fasta_file, output1)
write_to_file(qual_file, output2)
print ("End of script.")
Wie kann ich die "Länge der Zeilen aufaddieren", sodass diese dem Speicherverbrauch in der Datei (bzw. der Position des Cursors) entspricht?
Warum geht tell() nicht, bzw. was mache ich falsch?
Warum ist der Code so lahm?
Wie messe ich am besten die Zeit der kritischen for-Schleife?
@RedSharky: Anscheinend wird `tell()` bei neueren Python-Versionen "abgeschaltet" wenn man das Dateiobjekt als Iterator verwendet. Dann stimmt die `tell()`-Position nämlich nicht zwingend mit der aktuellen Zeile überein, weil nicht mehr zeilenweise über die C-Standardbibliothek gelesen wird, sondern Python selbst das einlesen und aufteilen in Zeilen übernimmt. Das ist gut, denn das ist schneller als das `readline()` auf C-Ebene. Aber halt auch "schlecht" weil man die Dateiposition nicht mehr mit der aktuellen Zeile in Verbindung bringen kann.
Du liest ja alles erst ein und verwendest dann auch noch ``+=`` auf den Daten. Das ist sicher langsamer und speicherhungriger als nötig. Zeichenketten sind unveränderbar, dass heisst für jede dieser Operationen wird neuer Speicher angefordert und die alte und die neue Zeichenkette werden dort hinkopiert. Je länger das Programm läuft um so mehr Daten werden unnötig pro Zeile im Speicher herumkopiert und um so langsamer wird es.
Wenn das alles mal im Speicher sein muss, dann solltest Du die Zeilen in Listen sammeln und am Ende die Listen mit `writelines()` rausschreiben. Allerdings nur wenn die Daten wirklich alle auch im Speicher benötigt werden, sonst könntest Du doch gleich die Zeilen wegschreiben!?
Das manuelle hochzählen von `counter` könnte man mit `enumerate()` vermeiden. Und `dest` als Zahlen ist eine unnötige Indirektion. Man könnte auch direkt die Liste oder das Dateiobjekt, welches das Ziel der Zeile ist, an den Namen binden.
Die Entscheidung bei '@' und '+' ist zu umständlich. Die erste Zeile in den beiden ersten Zweigen kann weg wenn man den Code im ``else`` nicht in das ``else`` sondern einfach nach dem ``if``/``elif`` stehen hat. Und da ist wie gesagt das ``if``/``elif`` überflüssig wenn man gleich `dest` an das entsprechende Objekt bindet.
Du liest ja alles erst ein und verwendest dann auch noch ``+=`` auf den Daten. Das ist sicher langsamer und speicherhungriger als nötig. Zeichenketten sind unveränderbar, dass heisst für jede dieser Operationen wird neuer Speicher angefordert und die alte und die neue Zeichenkette werden dort hinkopiert. Je länger das Programm läuft um so mehr Daten werden unnötig pro Zeile im Speicher herumkopiert und um so langsamer wird es.
Wenn das alles mal im Speicher sein muss, dann solltest Du die Zeilen in Listen sammeln und am Ende die Listen mit `writelines()` rausschreiben. Allerdings nur wenn die Daten wirklich alle auch im Speicher benötigt werden, sonst könntest Du doch gleich die Zeilen wegschreiben!?
Das manuelle hochzählen von `counter` könnte man mit `enumerate()` vermeiden. Und `dest` als Zahlen ist eine unnötige Indirektion. Man könnte auch direkt die Liste oder das Dateiobjekt, welches das Ziel der Zeile ist, an den Namen binden.
Die Entscheidung bei '@' und '+' ist zu umständlich. Die erste Zeile in den beiden ersten Zweigen kann weg wenn man den Code im ``else`` nicht in das ``else`` sondern einfach nach dem ``if``/``elif`` stehen hat. Und da ist wie gesagt das ``if``/``elif`` überflüssig wenn man gleich `dest` an das entsprechende Objekt bindet.
Mal eine ungetestete Variante wie man das effizienter lösen könnte:
Code: Alles auswählen
def process(lines, process_at, process_plus):
process_line = lambda line: None
for line in lines:
if line.startswith('@'):
process_line = process_at
elif line.startswith('+'):
process_line = process_plus
process_line(line)
def main():
with open(IN_FILENAME, 'r') as lines:
with open(FASTA_FILENAME, 'w') as fasta_file:
with open(QUAL_FILENAME, 'w') as qual_file:
process(lines, fasta_file.write, qual_file.write)
Ich hab einige gute Tipps eingearbeitet. Das Programm rennt nun in 10 Sekunden durch - ist also um Dimensionen schneller als zuvor! Danke!! So richtig verstanden hab ich das aber nicht.
Code: Alles auswählen
import time
import string
IN_FILENAME = "sulcia.fastq"
FASTA_FILENAME = "sulcia.fas"
QUAL_FILENAME = "sulcia.q"
def process(lines, fasta_file, qual_file):
t1 = time.time()
for i,line in enumerate(lines):
t2 = time.time()
if (t2-t1) > 1:
print (time.strftime('%X'), ":", i, "lines processed")
t1 = t2
if line.startswith('@') and not line.startswith('@@'):
fasta_file.write(line.replace('@','>'))
destination = "fasta"
elif line.startswith('+'):
qual_file.write(line)
destination = "qual"
else:
if destination == "fasta":
fasta_file.write(line)
if destination == "qual":
qual_file.write(line)
def main():
with open(IN_FILENAME, 'r') as lines:
with open(FASTA_FILENAME, 'w') as fasta_file:
with open(QUAL_FILENAME, 'w') as qual_file:
process(lines, fasta_file, qual_file)
if __name__ == "__main__":
print("fastq2fasANDq v0.5")
print("This script splits the data of fastq files into two seperate files.")
main()
print ("End of script.")
Noch eine kleine Vereinfachung:
Code: Alles auswählen
if line.startswith('@') and not line.startswith('@@'):
fasta_file.write(line.replace('@','>'))
destination = fasta_file
elif line.startswith('+'):
qual_file.write(line)
destination = qual_file
else:
destination.write(line)
Das Leben ist wie ein Tennisball.
Noch mal zurück zum eigentlichen Thema:
In der for-Schleife addiere ich nun einfach die aktuelle Zeilen-Größe auf (cur_size). Das stimmt dann am Ende mit der Dateigröße überein - allerdings nicht immer hundertprozentig genau.
In der for-Schleife addiere ich nun einfach die aktuelle Zeilen-Größe auf (cur_size). Das stimmt dann am Ende mit der Dateigröße überein - allerdings nicht immer hundertprozentig genau.
Code: Alles auswählen
import os
import time
IN_FILENAME = "sequence.fastq"
FASTA_FILENAME = "sequence.fas"
QUAL_FILENAME = "sequence.q"
def process(lines, fasta_file, qual_file):
cur_size = 0
file_size = os.path.getsize(IN_FILENAME)
t1 = time.time()
for i,line in enumerate(lines):
#current progress
cur_size += len(line)
#feedback
t2 = time.time()
if (t2-t1) > 1:
print (time.strftime('%X'), ":", i, \
"lines processed ("+str('{0:.1f}'.format(cur_size*100/file_size))+" %)")
t1 = t2
#logic
if line.startswith('@') and not line.startswith('@@'):
fasta_file.write(line.replace('@','>'))
destination = fasta_file
elif line.startswith('+'):
qual_file.write(line)
destination = qual_file
else:
destination.write(line)
print (time.strftime('%X'), ":", i, \
"lines processed ("+str('{0:.1f}'.format(cur_size*100/file_size))+" %)")
def main():
with open(IN_FILENAME, 'r') as lines:
with open(FASTA_FILENAME, 'w') as fasta_file:
with open(QUAL_FILENAME, 'w') as qual_file:
process(lines, fasta_file, qual_file)
if __name__ == "__main__":
print("fastq2fasANDq v0.7")
print("This script splits the data of fastq files into two seperate files.")
main()
print ("End of script.")