Memory Maker

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Ein sinnvolleres Projekt als beim letzten mal, wieder einmal in 3.x. Es findet doppelte Dateien und löscht sie, die Geschwindigkeit finde ich akzeptabel, bei vielen Doppelten Dateien kann das aber lange dauern. :roll:
Hier das schlecht geschriebene Modul:

Code: Alles auswählen

import os
import os.path
import filecmp

allfiles = {}
doublefiles = []

def scandirectory(path):
    for root,dirs,files in os.walk(path):
        for file in files:
            if os.path.splitext(file)[1] not in allfiles.keys():
                allfiles[os.path.splitext(file)[1]] = []
            allfiles[os.path.splitext(file)[1]].append([os.path.getsize(os.path.join(root, file)), os.path.join(root, file)])

def finddoublefiles():
    run_again = False
    for filetype in allfiles:
        for file in allfiles[filetype]:
            to_check_file = file
            for i in range(len(allfiles[filetype])):
                file = allfiles[filetype][i]
                if not to_check_file == file and to_check_file[0] == file[0]:
                    if filecmp.cmp(to_check_file[1], file[1]):
                        doublefiles.append(file[1])
                        allfiles[filetype].remove(file)
                        run_again = True
                        break
            if run_again:
                break
        if run_again:
            break
    if run_again:
        finddoublefiles()

def deletedoublefiles():
    for file in doublefiles:
        os.remove(file)

def resetfilelist():
    global allfiles, doublefiles
    allfiles = {}
    doublefiles = []
Und der GUI dazu:

Code: Alles auswählen

import tkinter
import tkinter.filedialog as filedialog
import tkinter.messagebox as messagebox
import memorymaker
import os

window = tkinter.Tk()
window.title('Memory Maker')

tkinter.Label(window, text='Startus:').grid(row=0, column=0, sticky='e')
startuslabel = tkinter.Label(window, text='Keine Aktionen verbleibend', width=50, fg='black', bg='#00ffff', font=('bold'))
startuslabel.grid(row=0, column=1, sticky='w')
infolabel = tkinter.Label(window, text='Alles OK', width=50, fg='black', bg='green', font=('bold'))
infolabel.grid(row=0, column=1, sticky='e')
tkinter.Label(window, text='Info:').grid(row=0, column=1)

doublefile_listbox = tkinter.Listbox(window, width=157, height=47)
doublefile_listbox.grid(row=1, column=1)
scrollbar = tkinter.Scrollbar(window)
scrollbar.grid(row=1, column=2, sticky='S'+'N')
doublefile_listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=doublefile_listbox.yview)

def appendpath():
    path = filedialog.askdirectory()
    if path != '':
        startuslabel.config(text='Verzeichnis wird gescannt.')
        window.update_idletasks()
        memorymaker.scandirectory(path)
        startuslabel.config(text='Keine Aktionen verbleibend')
        infolabel.config(text='Es wurde noch nicht nach Doppelten Dateien Gesucht.', bg='#ffff00')

def finddoublefiles():
    startuslabel.config(text='Suche nach Doppelten Dateien.')
    window.update_idletasks()
    memorymaker.doublefiles = []      #notwendig um die listbox nicht löschen zu müssen.
    try:
        memorymaker.finddoublefiles()
        for path in memorymaker.doublefiles:
            doublefile_listbox.insert('end', path)
    except IOError:
        infolabel.config(text='Datei Zugriffs Fehler', bg='#ff0000')
    else:
        startuslabel.config(text='Keine Aktionen verbleibend')
        infolabel.config(text='Alles OK', bg='#00ff00')

def resetall():
    if messagebox.askyesno('Memory Maker', 'Alle Datei-Informationen gehen verlorren!'):
        memorymaker.resetfilelist()
        doublefile_listbox.delete(0, 'end')

def deletedoublefiles():
    if messagebox.askyesno('Memory Maker', 'Dateien wirklich löschen?'):
        try:
            for path in doublefile_listbox.get(0, 'end'):
                os.remove(path)
        except:
            infolabel.config(text='Datei Zugriffs Fehler', bg='#ff0000')
        else:
            doublefile_listbox.delete(0, 'end')         

def clearmessagelabel():
    infolabel.config(text='Alles OK', bg='#00ff00')

tkinter.Button(window, text='Entfernen',
               command=lambda doublefile_listbox=doublefile_listbox: \
               doublefile_listbox.delete(tkinter.ANCHOR)).grid(row=1, column=0, sticky='s'+'n'+'e'+'w')

menubar = tkinter.Menu(window)
filemenu = tkinter.Menu(menubar)
filemenu.add_command(label='Verzeichnis Scannen', command=appendpath)
filemenu.add_command(label='Doppelte Dateien Suchen', command=finddoublefiles)
filemenu.add_separator()
filemenu.add_command(label='Zurücksetzen', command=resetall)
filemenu.add_separator()
filemenu.add_command(label='Doppelte Dateien Löschen', command=deletedoublefiles)
filemenu.add_separator()
filemenu.add_command(label='Beenden', command=window.destroy)

menubar.add_cascade(label='Datei', menu=filemenu)
menubar.add_command(label='Nachricht gelessen', command=clearmessagelabel)
window.config(menu=menubar)

tkinter.mainloop()

Oh, und Entschuldigung hin voraus.
Zuletzt geändert von Py-Prog am Freitag 27. Mai 2011, 20:06, insgesamt 2-mal geändert.
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Warum sortierst du das nach Dateiendung? "foo.jpg" und "bar.txt" können ja auch den selben Inhalt haben :-) Außerdem würde es Sinn machen, erstmal die Dateigrößen zu vergleichen, weil man dazu die Dateien nicht auslesen muss.

Du könntest auch, anstatt Dateiinhalte zu vergleichen, Hashsummen dieser bilden. Das hätte den Vorteil, dass man eine Datei höchstens einmal auslesen muss, weil Hashsummen so klein sind, dass man sie locker im Speicher behalten kann.

Man könnte zum Beispiel den Verzeichnisbaum in ein ``Dateigröße:(Name, Hash)``-Dictionary einlesen. (Den Hash berechnet man zum spätest möglichen Zeitpunkt, am Anfang ist er also noch undefiniert.)

Code: Alles auswählen

from collections import defaultdict
files = defaultdict(list)
for filename in ...:
  files[os.path.getsize(filename)].append((filename, None))

def find_duplicate(filename, filehash):
  size = os.path.getsize(filename)
  possible_duplicates = files.get(size)
  if possible_duplicates is None:
    return False
  for index, (name, hashsum) in enumerate(possible_duplicates):
    if hashsum is None:
      hashsum = calculate_file_hash(filename)
      possible_duplicates[index] = (name, hashsum)
    if hashsum == filehash:
      yield name
So in etwa.

Anstatt diesem `scandirectory` würde ich übrigens `os.walk` verwenden.

`some_key in some_dictionary.keys()` ist das Selbe wie `some_key in some_dictionary` (zweite If-Klausel in `scandirectory`)

Code: Alles auswählen

with ...:
  with ....:
lässt sich auch schreiben als

Code: Alles auswählen

with ..., ...:
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Dauerbaustelle hat geschrieben:Warum sortierst du das nach Dateiendung? "foo.jpg" und "bar.txt" können ja auch den selben Inhalt haben :-) Außerdem würde es Sinn machen, erstmal die Dateigrößen zu vergleichen, weil man dazu die Dateien nicht auslesen muss.

Du könntest auch, anstatt Dateiinhalte zu vergleichen, Hashsummen dieser bilden. Das hätte den Vorteil, dass man eine Datei höchstens einmal auslesen muss, weil Hashsummen so klein sind, dass man sie locker im Speicher behalten kann.

Man könnte zum Beispiel den Verzeichnisbaum in ein ``Dateigröße:(Name, Hash)``-Dictionary einlesen. (Den Hash berechnet man zum spätest möglichen Zeitpunkt, am Anfang ist er also noch undefiniert.)
Tut mir leid aber da muss ich dir wieder sprechen:
1) ja *.jpg und *.txt könnten den Gleichen inhalt haben, ... (denk einfach mal nach wieso)
2)Wie willst du denn wissen welche Dateien gleich sind oder nicht
3)zwei verschiedene Dateien können die Gleiche Hash summe ergeben und ich verschwende die Rechenleistung bei jeder Datei
4)ich hab schon eine Möglichkeit gefunden den Scan zu beschleunigen.

Der Code ist zwar nicht gut geschrieben aber die Logik passt :!:

Gibts ausser den zwei "Fehlern" sonnst noch was zu verbessern?
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Py-Prog hat geschrieben:1) ja *.jpg und *.txt könnten den Gleichen inhalt haben, ... (denk einfach mal nach wieso)
Warum vergleichst du dann nur Dateien der gleichen Endung?
2)Wie willst du denn wissen welche Dateien gleich sind oder nicht
Ich nehme mal an, dass du von dem Größenvergleich sprichst? Wenn die beide unterschiedlich groß sind, können sie schonmal nicht gleich sein :-)
3)zwei verschiedene Dateien können die Gleiche Hash summe ergeben und ich verschwende die Rechenleistung bei jeder Datei
Beides falsch. Zwei unterschiedliche Inhalte ergeben in der Praxis niemals die gleiche Hashsumme. Das ist ja gerade der Sinn von Hashes. Außerdem verschwendest du überhaupt keine Rechenleistung, nein du sparst sogar welche, weil du die Hashsummen der Dateien im Speicher behalten kannst und sie somit maximal ein einziges Mal auslesen musst (die aktuelle Version deines Scripts liest Dateien mehrfach aus).
BlackJack

@Dauerbaustelle: Natürlich können in der Praxis auch zwei Dateien mit unterschiedlichem Inhalt den gleichen Hash-Wert ergeben. Wenn man sicher gehen will, muss man die Dateien dann auch tatsächlich vergleichen und kann einen Hash-Wert auch nur zum Ausschliessen von Vergleichskandidaten verwenden. Ausserdem kann es durchaus sein, dass es ohne Hash-Werte schneller geht, denn für den Hash-Wert über die ganze Datei muss man ja die ganze Datei einlesen. Bei Vergleichen von Dateien kann man beim ersten Unterschied das Lesen abbrechen.

Die Wahrscheinlichkeit bei den Hash-Werten aus versehen zwei Dateien als gleich zu betrachten ist zwar gering, aber gerade bei einem Programm das Kandidaten zum *löschen* suchen soll, wäre ich lieber auf der sicheren Seite, statt am Ende zwar den tooootal unwahrscheinlichen Fall, der ja niiieee eintritt bei einer wichtigen, nicht einfach ersetzbaren Datei zu haben.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

BlackJack hat geschrieben:@Dauerbaustelle: Natürlich können in der Praxis auch zwei Dateien mit unterschiedlichem Inhalt den gleichen Hash-Wert ergeben. Wenn man sicher gehen will, muss man die Dateien dann auch tatsächlich vergleichen und kann einen Hash-Wert auch nur zum Ausschliessen von Vergleichskandidaten verwenden.
Äh ja. Theoretisch könnten sie das in der Praxis. Die Wahrscheinlichkeit ist aber so gering, dass es in der Praxis keine Rolle spielt. Das ist ja der Sinn von Hashes.
Ausserdem kann es durchaus sein, dass es ohne Hash-Werte schneller geht, denn für den Hash-Wert über die ganze Datei muss man ja die ganze Datei einlesen. Bei Vergleichen von Dateien kann man beim ersten Unterschied das Lesen abbrechen.
Das hatte ich nicht bedacht. Haste natürlich Recht.
Die Wahrscheinlichkeit bei den Hash-Werten aus versehen zwei Dateien als gleich zu betrachten ist zwar gering, aber gerade bei einem Programm das Kandidaten zum *löschen* suchen soll, wäre ich lieber auf der sicheren Seite, statt am Ende zwar den tooootal unwahrscheinlichen Fall, der ja niiieee eintritt bei einer wichtigen, nicht einfach ersetzbaren Datei zu haben.
Jagut, über solche Daten lass ich eh keine Software laufen.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wenn man viel löschen muss, kann es aber durchaus sinnvoll sein, ein kleines Programm dafür zu schreiben. Halt notfalls eines, in dem Sicherheit vor Performance geht.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Ich glaube einige haben sich meinen Code nicht richtig angeschaut, er über prüft doch nur Dateien mit der Selben Größe! Aber ich denke das hat sich jetzt alles geklärt.

Was wäre eigentlich wenn man immer am Anfang und am ende einer Datei was einlesen würde und das dann vergleichen, in der "mitte" wäre auch noch möglich. Natürlich kann das auch zu falschen Ergebnissen führen aber auch sehr selten und es würde einiges an Performance sparen.
Karl Valentin hat geschrieben:Sicherheit geht vor Seltenheit!
Äh, und was ist jetzt mit dem Quell-Code? Passt der so oder kann ich da noch was verbessern?
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

`scandirectory`: Das `global` ist unnötig. Du solltest `os.walk` verwenden. Schau dir außerdem `collections.defaultdict` an.

`finddoublefiles`: Viel zu viele Einrückungsebenen. Versuche, den Code in mehrere Teilfunktionen aufzuteilen und/oder Einrückungstiefe durch geschicktes Verwenden von `break` und `continue` zu reduzieren. Außerdem kannst du anstatt des `range(len(...))`-Gedönz einfach `for filename in allfiles[filetype]` verwenden. Die zwei with-Statements lassen sich zu einem kombinieren. Das Größe-Argument an `read` sollte eine Zweierpotenz sein. Manuelles `seek`-en ist nicht nötig. Statt des manuellen Vergleichens könntest du übrigens auch das `filecmp`-Modul verwenden.

Der Sinn der Sortierung nach Dateinamensendung erschließt sich mir immer noch nicht. Man würde hier eher ein "Größe: Dateinamenliste"-Dictionary verwenden.

`resetfilelist` funktioniert nicht so, wie du dir das denkst, weil dabei nicht der Wert der globalen Variablen verändert wird, sondern neue lokale Variablen angelegt werden.


Allgemein solltest du die globalen Variablen ganz loswerden und stattdessen lieber Funktionsparameter verwenden.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Bei AutoIt kann man hinter break eine Zahl setzen. Wenn man z. B. 10 schleifen verschachtelt kann man mit einer Zeile die Schleife verlassen. Geht das auch in Python?
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

nö.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ich finde auch Control-Flow mit breaks, besonders verschachtelten oftmals schwer nachvollziehbar. Da würde ich den Code einfach etwas anders schreiben.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

@Dauerbaustelle danke für die Tipps, beim Scannen von den Dateien ist es jetzt etwas schneller.
Ich hoffe es passt so. (Der Code ist im ersten Beitrag)

Ihr werdet es ja nicht gemerkt haben weil ihr den Code nicht ausführt aber wenn ich nach doppelten Dateien suche und das Fenster kurz weg klicke und dann wieder aufrufe wird der Menü Balken weiß weil tkinter den GUI nicht Aktualisieren kann. Kann man den GUI irgend wie "Sperren" dass das nicht passiert?
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Und was ist hiermit?
Dauerbaustelle hat geschrieben:Schau dir außerdem `collections.defaultdict` an.

`finddoublefiles`: Viel zu viele Einrückungsebenen. Versuche, den Code in mehrere Teilfunktionen aufzuteilen und/oder Einrückungstiefe durch geschicktes Verwenden von `break` und `continue` zu reduzieren. Außerdem kannst du anstatt des `range(len(...))`-Gedönz einfach `for filename in allfiles[filetype]` verwenden.

Der Sinn der Sortierung nach Dateinamensendung erschließt sich mir immer noch nicht. Man würde hier eher ein "Größe: Dateinamenliste"-Dictionary verwenden.

Allgemein solltest du die globalen Variablen ganz loswerden und stattdessen lieber Funktionsparameter verwenden.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Dauerbaustelle hat geschrieben:Außerdem kannst du anstatt des `range(len(...))`-Gedönz einfach `for filename in allfiles[filetype]` verwenden.
Hab ich jetzt doch hinbekommen. Und neben bei auch die namen in den for-schleifen verbessert.
Dauerbaustelle hat geschrieben: Allgemein solltest du die globalen Variablen ganz loswerden und stattdessen lieber Funktionsparameter verwenden.
Ich weiß nicht was dass bringen soll, was ist falsch an den globalen Variablen.

Ich werd's es mit collections.defaultdict versuchen.
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Py-Prog hat geschrieben:Ich weiß nicht was dass bringen soll, was ist falsch an den globalen Variablen.
Ist einfach schlechter Stil. Produziert Spagehetticode. Die Forensuche ist dein Freund :-)
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Wie so ist für ein OS *.jpg nicht gleich *.JPG? Bei meiner Webseite war das auch so, der Link zu einer Datei war richtig geschrieben und nur wegen der Endung wurde das Bild nicht geladen. Ich wollte zwar eh es so umschreiben das z. B. *.jpg und *.jpeg gleich verarbeitet werden.
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Dauerbaustelle hat geschrieben:Ist einfach schlechter Stil. Produziert Spagehetticode. Die Forensuche ist dein Freund :-)
Ich hab nichts gefunden, wie so ist das ein schlechter Stil?
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Ist das immer noch Spagetti-Code?

Code: Alles auswählen

def finddoublefiles():
    run_again = False
    for filetype in allfiles:
        for to_check_file in allfiles[filetype]:
            for file in allfiles[filetype]:
                if not to_check_file == file and to_check_file[0] == file[0]:
                    if filecmp.cmp(to_check_file[1], file[1]):
                        doublefiles.append(file[1])
                        allfiles[filetype].remove(file)
                        run_again = True
                        break
            if run_again:
                break
        if run_again:
            break
    if run_again:
        finddoublefiles()
Ich hab zwar schon angefangen das anders zu machen aber irgendwie erscheint mir das besser.
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Py-Prog hat geschrieben:Wie so ist für ein OS *.jpg nicht gleich *.JPG? Bei meiner Webseite war das auch so, der Link zu einer Datei war richtig geschrieben und nur wegen der Endung wurde das Bild nicht geladen. Ich wollte zwar eh es so umschreiben das z. B. *.jpg und *.jpeg gleich verarbeitet werden.
Weil nicht alle Betriebssysteme Dateinsysteme case-insensitive handhaben... wenn ich mich recht entsinne, ist Windows sogar das einzige moderne Betriebssystem, das das tut.
Antworten