Seite 1 von 2
Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:03
von sfx2k
Hallo,
meine Ziel ist es, aus einer Datei alle Zeilen zu löschen, die mindestens ein Wort aus einer festgelegten Liste enthalten.
Ich weiß nicht, ob das der korrekte Weg für mein Vorhaben ist, aber ich lese die Datei mit readlines() in eine Liste ein, und erzeuge mittels List-Comprehension eine neue Liste der Zeilen, die die Begriffe nicht enthalten.
Das funktioniert auch halbwegs
Code: Alles auswählen
to_delete = ['ich', 'du', 'er', 'sie', 'es']
fo = open('C:\\Download\\testdatei.txt', 'r')
lines = fo.readlines()
fo.close()
for s in to_delete:
lines = [x for x in lines if x.lower().find(s) == -1]
print(lines)
fo = open('C:\\Download\\testdatei.txt', 'w')
fo.writelines(lines)
fo.close()
Halbwegs deshalb, weil auch der Begriff 'nicht' gefunden wird, wenn ich nach 'ich' suche - ich möchte aber nur nach ganzen Wörtern suchen.
Wäre hier die Suche über Reguläre Ausdrücke sinnvoller, als
in x oder
x.find() ?
Oder ist der Weg über die Listen vielleicht gar nicht der Optimale?
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:30
von BlackJack
@sfx2k: Wenn Du nur ganze Worte testen möchtest, dann musst Du die jeweilige Zeile in Worte zerlegen und dann prüfen. Oder mit regulären Ausdrücken arbeiten die sicherstellen das nur ganze Worte verglichen werden.
Die Reihenfolge der Verarbeitung ist ungünstig. Du hast eine kurze Liste mit Worten die eine feste Länge hat, und eine sehr wahrscheinlich längere Liste mit Zeilen die Du a) komplett in den Speicher liest, b) für jedes Wort erneut durchgehst und einiges mit der Zeile anstellst um zu testen ob das Wort enthalten ist. Anders herum wäre es sicher effizienter: Nur einmal über die Zeilen in der Datei iterieren und für jede Zeile testen ob eines der Worte enthalten ist. So könnte man sich am Ende mit Hilfe einer temporären Datei sogar das komplette einlesen aller Zeilen auf einen Schlag in den Arbeitsspeicher ersparen.
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:38
von sfx2k
@Blackjack:
Danke für Deine Antwort. Ich werde hinsichtlich der Suche dann mal optimieren.
Ist es denn zielführend, das so zu machen, also über eine Liste, oder gibt es bessere Wege?
Und was meinst Du mit
BlackJack hat geschrieben:So könnte man sich am Ende mit Hilfe einer temporären Datei sogar das komplette einlesen aller Zeilen auf einen Schlag in den Arbeitsspeicher ersparen.
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:46
von Sirius3
@sfx2k: Probleme, wie filtere alle Zeilen einer Datei nach einem bestimmten Kriterium gibt es ja häufiger:
Code: Alles auswählen
INPUT_FILE = r'C:\Download\testdatei.txt'
OUTPUT_FILE = r'C:\Download\testdatei.txt'
def some_filter(line):
return len(line)%2 == 0
def main():
with open(INPUT_FILE, 'r') as input_lines:
with open(OUTPUT_FILE, 'w') as output_file:
for line in input_lines:
if some_filter(line):
ouput_file.write(line)
if __name__ == '__main__':
main()
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:47
von cofi
Zielfuehrend ist es natuerlich: Du hast am Ende alle Zeilen die erhalten bleiben sollen.
Wenn du aber eigentlich nur eine neue Datei in derselben Reihenfolge schreiben willst und gar nicht alle neuen Zeilen im Speicher brauchst, ist es Overkill.
Da kommt der "bessere" Weg ins Spiel, den du nicht verstanden hast
Code: Alles auswählen
with open('pruned', 'w') as pruned:
with open('origin') as origin:
for line in origin:
parts = line.split()
if not any(w in parts for w in to_delete):
pruned.write(line)
Oder eben mit einer temporaeren Datei (siehe `tempfile` Modul), wenn du zwar keine neue Datei brauchst, aber auch nur einen sequenziellen bzw zeilenweisen Zugriff brauchst.
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:48
von nezzcarth
Den Ausgangsansatz könnte man vielleicht so ähnlich schreiben wie dies:
(python3)
Code: Alles auswählen
def main():
to_delete = {'ich', 'du', 'er', 'sie', 'es'}
with open('test_f.txt') as f:
result = [line.strip() for line in f.readlines() if not any(map(lambda x: x in to_delete, line.split())) ]
print(result)
if __name__ == '__main__':
main()
Du hattest ja von Listcomprehensions gesprochen, daher auf diese Weise; zwei For-Schleifen wären vielleicht einfacher zu überblicken. In jedem Fall würde man statt der Liste eine Menge nehmen, und schauen, dass man das Einlesen in einem with unterbringt.
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:52
von BlackJack
@nezzcarth: Da würde ich statt des `map()` + ``lambda`` einen Generatorausdruck schreiben. Das ist nur ein Zeichen mehr zu tippen wenn man bei einem einbuchstabigen Namen für das Wort bleibt, und IMHO einfacher zu lesen.
Und das ``f.read().split('\n')`` ist quatsch. Warum nicht einfach nur ``f`` an der Stelle? Wegen der Zeilenenden?
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 10:59
von nezzcarth
@BlackJack:
Ja, du hast recht, danke für den Hinweis. Mit deinen Vorschlägen sähe es dann vielleicht so aus:
Code: Alles auswählen
def main():
to_delete = {'ich', 'du', 'er', 'sie', 'es'}
with open('test_f.txt') as f:
result = [line.strip() for line in f if not any(item in to_delete for item in line.split())]
print(result)
if __name__ == '__main__':
main()
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 11:02
von sfx2k
@ BlackJack, Sirius3, cofi und nezzcarth:
Besten Dank für Eure Hinweise.
Der genannte 'bessere' Weg scheint mir dann doch am sinnvollsten für mein Vorhaben

Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 12:55
von sfx2k
Nur zur Info; so sieht es jetzt aus:
Code: Alles auswählen
# ******************************************************************************
INPUT_FILE = r'C:\Download\testdatei - Kopie.txt'
OUTPUT_FILE = r'C:\Download\new.txt'
# ******************************************************************************
def filter(line):
to_delete = ['ich', 'du', 'er', 'sie', 'es']
parts = line.lower().split()
return not any(w in parts for w in to_delete)
# ******************************************************************************
def main():
with open(INPUT_FILE, 'r') as input_lines:
with open(OUTPUT_FILE, 'w') as output_file:
for line in input_lines:
if filter(line):
output_file.write(line)
# ******************************************************************************
if __name__ == '__main__':
main()
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 13:51
von nezzcarth
Meiner Ansicht nach wäre der passende Datentyp für 'to_delete' ein Set, keine Liste, weil du keine Sortierung oder doppelte Einträge brauchst. Weiterhin würde ich diese 'Blacklist' nicht in die Funktion aufnehmen, sondern als Parameter in die Signatur schreiben; die spezifische Ausgestaltung kannst du ja als Konstante auf Modulebene setzen und übergeben, das wäre denke ich sauberer. Filter ist auch kein guter Name, weil es schon ein gleichnamiges Builtin gibt.
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 13:54
von mcdwerner
@sfx2k:
bin neugierig
funktioniert dein filter() mit der Zeile
"mich freut es, dass wir hier sind"
so wie von Dir gewünscht?
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 18:12
von Hyperion
Man kann sich auch eine Einrückungsebene noch sparen:
Code: Alles auswählen
In [13]: cat foo
Hallo
Welt!
In [14]: with open("foo") as infile, open("bar", "w") as outfile:
....: outfile.write(infile.read())
....:
In [15]: cat bar
Hallo
Welt!
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 20:08
von sfx2k
Hallo Ihr drei,
vielen Dank für Eure Antworten.
nezzcarth hat geschrieben:
Meiner Ansicht nach wäre der passende Datentyp für 'to_delete' ein Set, keine Liste, weil du keine Sortierung oder doppelte Einträge brauchst. Weiterhin würde ich diese 'Blacklist' nicht in die Funktion aufnehmen, sondern als Parameter in die Signatur schreiben; die spezifische Ausgestaltung kannst du ja als Konstante auf Modulebene setzen und übergeben, das wäre denke ich sauberer. Filter ist auch kein guter Name, weil es schon ein gleichnamiges Builtin gibt.
Habe Deine Vorschläge so umgesetzt
mcdwerner hat geschrieben:
@sfx2k:
bin neugierig
funktioniert dein filter() mit der Zeile
"mich freut es, dass wir hier sind"
so wie von Dir gewünscht?
Hehe, nein - natürlich nicht

Ich bin daher mal auf Reguläre Ausdrücke umgestiegen.
Hyperion hat geschrieben:Man kann sich auch eine Einrückungsebene noch sparen:
Ok, gespart
Code: Alles auswählen
import re
# ******************************************************************************
INPUT_FILE = r'C:\Download\testdatei - Kopie.txt'
OUTPUT_FILE = r'C:\Download\testdatei - Kopie_neu.txt'
TO_DELETE = set(['ich', 'du', 'er', 'sie', 'es'])
# ******************************************************************************
def some_filter(line, args):
result = True
for arg in args:
if re.search('{1}{0}{1}'.format(arg, r'\b'), line, re.I):
result = False
break
return result
# ******************************************************************************
def main():
with open(INPUT_FILE, 'r') as input_lines, open(OUTPUT_FILE, 'w') as \
output_file:
for line in input_lines:
if some_filter(line, TO_DELETE):
output_file.write(line)
# ******************************************************************************
if __name__ == '__main__':
main()
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 20:25
von Sirius3
@sfx2k: So baut man keine reguläre Ausdrücke zusammen. Wenn Du irgendwann nach Strings suchst, die Sonderzeichen enthalten, dann funktioniert die Filterfunktion nicht mehr wie gewünscht, und der Fehler ist nicht schwer zu finden.
Deshalb:
Code: Alles auswählen
def some_filter(line, args):
return not re.search(r'\b({})\b'.format('|'.join(map(re.escape, args))), line. re.I)
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 20:36
von BlackJack
@sfx2k: Hier macht das `set` nun aber wieder keinen Sinn mehr. Da wäre eine Liste angebrachter weil das eine einfacherere Datenstruktur ist und Du von der Funktionalität vom `set` nichts verwendest.

Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Mittwoch 17. September 2014, 21:00
von BlackJack
Noch eine Variante das Ganze aufzuteilen. Eine Funktion zum Lesen, eine die eine einzelne Zeile prüft, und eine zum Schreiben des Ergebnis, und dann noch eine Hauptfunktion welche die drei Teile kombiniert:
Code: Alles auswählen
#!/usr/bin/env python
import re
from itertools import ifilterfalse
def iter_lines(filename):
with open(filename, 'r') as lines:
for line in lines:
yield line
def create_line_test(words):
return re.compile(
r'\b({0})\b'.format('|'.join(re.escape(word) for word in words)),
re.IGNORECASE
).search
def save_lines(filename, lines):
with open(filename, 'w') as out_file:
out_file.writelines(lines)
def main():
to_delete = ['ich', 'du', 'er', 'sie', 'es']
save_lines(
'test2.txt',
ifilterfalse(create_line_test(to_delete), iter_lines('test.txt'))
)
if __name__ == '__main__':
main()
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Donnerstag 18. September 2014, 07:43
von sfx2k
Sirius3 hat geschrieben:
Code: Alles auswählen
def some_filter(line, args):
return not re.search(r'\b({})\b'.format('|'.join(map(re.escape, args))), line. re.I)
Ha, das gefällt mir ausgezeichnet. Besonders die Möglichkeit, automatisch escapen zu lassen
BlackJack hat geschrieben:@sfx2k: Hier macht das `set` nun aber wieder keinen Sinn mehr. Da wäre eine Liste angebrachter weil das eine einfacherere Datenstruktur ist und Du von der Funktionalität vom `set` nichts verwendest.

Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
Okay, dann erkläre mir doch bitte, warum drei Posts vorher laut nezzcarth noch ein Set besser geeignet war als eine Liste, da ich deren Funktionalitäten gar nicht benötige, und jetzt genau andersrum?
Bezüglich Deines Quelltextes:
In Delphi hätte ich alles einzeln gemacht. Diese vielen verschachtelten Aufrufe - da muss ich erstmal mit klarkommen.
Besonders das
ifilterfalse ist mir noch nicht ganz klar. Naja - mal das Doc dazu lesen
Sirius3 benutzt zum Zusammensetzen des Regulären Ausdrucks die map() Funktion, Du eine for-Schleife.
Gibt es da für diesen Fall signifikante Unterschiede? Oder ist das einfach nur Geschmackssache?
Und dann noch eine grundlegende Frage, auf die ich aber keine Antwort finden konnte:
Muss ich, wenn ein File in einer with-Anweisung geöffnet wird, dieses nicht auch wieder schließen?
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Donnerstag 18. September 2014, 08:21
von Hyperion
sfx2k hat geschrieben:
Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
Okay, dann erkläre mir doch bitte, warum drei Posts vorher laut nezzcarth noch ein Set besser geeignet war als eine Liste, da ich deren Funktionalitäten gar nicht benötige, und jetzt genau andersrum?
Weil Du da noch *ohne* reguläre Ausdrücke geprüft hast, ob ein Wort in einer *Menge* vorkommt. Du hattest eine Liste genommen, die beim ``in`` Operator jedes Mal schlimmsten Falls komplett durchlaufen werden muss (das beschreibt man auch mit O(n)). Bei einer Menge kann der ``in`` Operator in *konstanter* Zeit (O(1)) ermitteln, ob ein Element in einer Liste ist.
Das kann man in einer Python-Shell (hier iPython) leicht nachprüfen:
Code: Alles auswählen
In [1]: values = list(range(10000000))
In [3]: some_value = 9999999
In [4]: timeit some_value in values
1 loops, best of 3: 305 ms per loop
In [5]: values = set(range(10000000))
In [6]: timeit some_value in values
10000000 loops, best of 3: 147 ns per loop
Beachte die Dauer - Millisekunden vs Nanosekunden! Meine Testzahl ist natürlich so schlecht wie möglich für die Liste gewählt, da sie dort die *letzte* Position hat. ``in`` muss also *alle* Einträge durchlaufen, bevor er auf die gewünschte Zahl trifft.
Auch eine nicht vorhandene Zahl muss natürlich immer alle Elemente durchlaufen.
Bei Sammlungen, die auf Hashing basieren (Sets, Dictionaries), kann dies *unabhängig* von der Größe der Sammlung immer in konstanter Zeit erreicht werden.
Edit: Noch einmal zur Verdeutlichung eine Reduktion der Anzahl um den Faktor 10 also von 10 Millionen auf eine Million Elemente. Man erkennt schön, wie der Listen basierte Zugriff sich *linear* um den Faktor reduziert (30ms vs 300ms), wohingegen der Mengen basierte Ansatz gleich geblieben ist:
Code: Alles auswählen
In [14]: some_value = 999999
In [15]: values = list(range(1000000))
In [16]: timeit some_value in values
10 loops, best of 3: 30.6 ms per loop
In [17]: values = set(range(1000000))
In [18]: timeit some_value in values
10000000 loops, best of 3: 145 ns per loop
sfx2k hat geschrieben:
Muss ich, wenn ein File in einer with-Anweisung geöffnet wird, dieses nicht auch wieder schließen?
Nein, eben nicht!

(Das ist ja das tolle; das passiert übrigens auch bei Ausnahmen!)
Re: Zeilen in einer Datei löschen über List-Comprehension
Verfasst: Donnerstag 18. September 2014, 08:44
von BlackJack
@sfx2k: Drei Posts vorher hat ein `set` noch etwas gebracht weil der ``in``-Operator bei Listen die Liste linear, Element für Element, vergleicht, und ein `set()` diese Anfrage ”sofort” beantworten kann, ohne sich alle enthaltenen Element einzeln anschauen zu müssen. Wenn man nur über die Elemente itertiert, dann verwendet man diese Eigenschaft von `set` nicht. Dann könnte man nur noch argumentieren, dass man sicherstellen möchte, dass die Testworte alle unterschiedlich sind, was man bei den wenigen Worten noch ganz gut ohne Unterstützung vom Programm hinbekommt.
Man kann es auch unverschachtelt(er) schreiben, wenn man sich Namen für die Zwischenergebnisse ausdenkt.
Code: Alles auswählen
def main():
to_delete = ['ich', 'du', 'er', 'sie', 'es']
lines = iter_lines('test.txt')
filtered_lines = ifilterfalse(create_line_test(to_delete), lines)
save_lines('test2.txt', filtered_lines)
Da gute Namen finden, in der Regel schwieriger ist als den Code an sich zu schreiben, spare ich mir das gerne.
Bei den regülären Ausdrücken benutze ich einen Generatorausdruck. Da hätte ich in der Tat `map()` oder `itertools.imap()` verwenden können.
Die ``with``-Anweisung sorgt dafür das egal wo und wie der Programmfluss den Block verlässt, die `__exit__()`-Methode auf dem Objekt aufgerufen wird das nach dem ``with``-Schlüsselwort erzeugt wird. Also in diesem Fall die Datei. Und bei Dateien ist eine `__exit__()`-Methode implementiert, welche die Datei schliesst. Das ist aber auch *das* Beispiel für die ``with``-Anweisung. Da sollte man eigentlich etwas zu finden.