Liste löschen, aber n Stück auslassen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
The Bang 2
User
Beiträge: 14
Registriert: Montag 11. März 2013, 12:30

Doofer Titel, aber ich kanns nicht besser ausdrücken ;)

Ich habe hier ein Verzeichnis, in dem zyklisch Backups angelegt werden. Hierbei handelt es sich um Ordner und Dateien, die jeweils in einem bestimmten Schema beginnen und mit dem aktuellen Datum benannt sind. Für die Ordner wäre das z.B. heute z.B. BCK0409_0525 und für die Dateien P09042013.prg. In diesem Ordner möchte ich nun von Zeit zu Zeit den Inhalt löschen, aber eine gewisse Anzahl an aktuellen Backups behalten. Ich habe das folgendermaßen gelöst:

Code: Alles auswählen

def clean_nfs(path_to_clean, units_to_skip):
    """
    Loescht Ordner und Dateien in Verzeichnis "path_to_clean"
    """
##  Schritt 1 - Scanne Verzeichnisse und Ordner und packe diese in eine Liste
    files = []
    folders = []
    for name in os.listdir(path_to_clean):
        try:
            source = os.path.join(path_to_clean, name)
            if os.path.isdir(source):
                folders.append(name)
            else:
                files.append(name)
        except os.error as error:
            print (error)

## Schritt 2 - Sortiere Listen nach regelkonformen Anfangsbuchstaben und kuerze die letzen n raus
    folders = [s for s in folders if s.startswith('BCK')]
    files = [s for s in files if s.startswith('P')]
    n = len(folders)
    folders = folders[:n-units_to_skip]
    n = len(files)
    files = files[:n-units_to_skip]

## Schritt 3 - Loesche alle uebriggebliebenen Dateien und
    print ('Raume NFS-Server auf: ' + path_to_clean)
    for name in folders + files:
        try:
            source = os.path.join(path_to_clean, name)
            if os.path.isdir(source):
                shutil.rmtree(source)
            else:
                os.remove(source)
        except (shutil.Error, os.error) as error:
            print (error)
Das funktioniert, aber irgendwie kommt mir das mit dem jeweiligen Liste erstellen, zählen, abschneiden und anschließenden Löschen arg aufwändig vor. Gibt´s von euch einen Lösungsansatz um das besser zu lösen? Ps: Die Kommentare mittendrin fliegen am Ende raus, die sind nur für mein Verständnis ;)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du kannst z.B. Schritt 2 etwas vereinfachen, wenn du Pythons Möglichkeiten beim Slicing besser ausnutzt. Dort sind nämlich auch negative Indizes erlaubt. Anwendungsbeispiel:

Code: Alles auswählen

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(10)[:-2] # schneide die letzten 2 ab
[0, 1, 2, 3, 4, 5, 6, 7]
Bezogen auf deinen Code hieße das dann:

Code: Alles auswählen

folders = [s for s in folders if s.startswith('BCK')][:-units_to_skip]
files = [s for s in files if s.startswith('P')][:-units_to_skip]
Du hättest damit also den kompletten Schritt in zwei Zeilen abgearbeitet.

Zudem würde ich die `.startwith()`-Prüfungen schon oben in die erste `for`-Schleife reinsetzen und entsprechend nur bei positiven Ergebnis ein `.append()` machen. Weil warum sollte man dort Dinge reinpacken, obwohl man weiß, dass man sie kurz danach eh wieder rausschmeißt? ;)

Achja, und wenn man dies tut, dann kann man sich Schritt 2 komplett sparen und direkt zu Schritt 3 übergehen:

Code: Alles auswählen

for name in folders[:-units_to_skip] + files[:-units_to_skip]:
    # usw
Und wenn wir schon bei Schritt 3 sind, können wir auch gleich zur nächsten Verbesserungsmöglichkeit kommen: Da ja schon in Schritt 1 die nötige Filterung dank der nach oben geschobenen `.startswith()`-Prüfung vorgenommen wurde, kann man jetzt auch gleich den kompletten Pfad abspeichern und braucht nicht in Schritt 3 nochmal die Zusammensetzung des Pfades zu machen. Und dann würde ich auch einfach zwei Schleifen in Schritt 3 machen. Sähe dann letztlich so aus:

Code: Alles auswählen

for path in folders[:-units_to_skip]:
    shutil.rmtree(path)
for path in files[:-units_to_skip]:
    os.remove(path)
Das Debugging / Logging von OS-Fehlern, was du ja scheinbar mit den ganzen `print()`s machen willst, ließe sich auch als ein `try`-`except` um die gesamte Funktion erledigen - also seitens des Aufrufers der Funktion. Dann hat man an der Stelle auch eine etwas schönere Trennung der Verarbeitungsebenen (oder wie auch immer man das nennen möchte). Die `print()`s aus Schritt 1 könnten in dem Fall dann natürlich auch raus, wenn wie gesagt eine höhere Ebene dies übernimmt.
The Bang 2
User
Beiträge: 14
Registriert: Montag 11. März 2013, 12:30

Uff, das ist schon mal eine große Hilfe, vielen Dank :). Man kann ja doch viele Sachen in einem Aufwasch erledigen, man lernt halt immer was dazu :)

Ein Problem hab ich nun aber noch: Wenn ich deinen Vorschlag umsetze und mit folders.append(source) die Pfade direkt am Anfang in die Liste schreibe, haut irgendetwas mit dem Pfad nicht hin:
['C:\\Users\\User\\Dropbox\\Python Projektordner\\Projekt Arbeit-BCK\\Testverzeichnis\\nfs\\BCK0307_0525'
Ich kenne die Lösung, dass man bei einem String das ganze mit einem r umgehen kann, also beispielstring = r'Pfad/zur/Datei'.

Aber wie funktioniert das nun in meinem Fall mit einer Liste?
BlackJack

@The Bang 2: Das ist schon in Ordnung so. Du musst unterscheiden zwischen der Darstellung einer Zeichenkette in einer Liste und dem was die Zeichenkette tatsächlich enthält. In Listen wird die `repr()`-Darstellung verwendet, weil man nur dann bei jeder Zeichenkette wirklich sehen kann was sie für Werte enthält. Alles ausserhalb von ASCII wird als Escape-Sequenz dargestellt, also mit einem \. Was natürlich auch heisst das der \ selbst auch escaped dargestellt werden muss als \\ denn sonst gäbe es ja wieder Mehrdeutigkeiten.

Edit: Übrigens solltest Du die Listen vielleicht nach dem alter der Dateien sortieren bevor Du im Grunde beliebige davon löschst. `os.listdir()` garantiert keine besondere Ordnung auf den Dateinamen! Die können nach irgendeinem Kriterium sortiert sein, müssen das aber nicht!
Antworten