@Melewo: Ein paar Anmerkungen zu Deinem Programm (ist etwas lang geworden — nicht erschrecken

).
Das Modul lässt sich nicht importieren ohne dass das Programm los läuft und `instanz` ist eine globale Variable. Ersteres verhindert das man das Modul in einer Python-Shell importieren und einzelne Funktionen oder Klassen testen oder in anderen Modulen verwenden kann. Auch Werkzeuge die Informationen per ”introspection” aus dem Modul ziehen, wie Sphinx, das Dokumentationswerkzeug welches viele Python-Projekte verwenden und mit dem auch die Python-Dokumentation erstellt wird, funktionieren nur wenn man Module importieren kann. Ebenso Testwerkzeuge für Unit- und Doctests.
`instanz` ist kein guter Name, denn der ist so generisch das er für wirklich *jeden* Wert in Python geeignet ist. Mich persönlich stört auch, dass es eine falsche Übersetzung für „instance“ ist. Oder wäre, wenn man sich nicht der ”Dummheit” der Masse beugt.
Die Aufteilung des Codes in `Dateisuche` würde ich als falsch ansehen. Ein Objekt sollte nach Ablauf der `__init__()` in einem benutzbaren Zustand sein. Bei `Dateisuche` *muss* man zwingend die `layout()`-Methode aufrufen, wenn man das aber immer zwingend machen muss, dann stellt sich die Frage warum man den Benutzer der Klasse dazu zwingt. Ein guter Grund für eine Zweiteilung ist das die `__init__()` zum Aufrufer zurückkehren sollte, damit der gegebenfalls noch etwas mit dem Objekt machen kann, wenn er möchte. Also in diesem Fall bevor die GUI-Hauptschleife läuft. Aber nach der `__init__()` der Klasse die das Hauptfenster stellt, sollte das Hauptfenster auch komplett sein, soweit es diese Klasse angeht. Das heisst so ziemlich alles aus der `layout()`-Methode sollte im Zuge von `__init__()` passieren. Da die `layout()`-Methode dann nur noch den Aufruf der GUI-Hauptschleife enthält, wäre dafür der Name `mainloop()` passender. Und man könnte sich diese Methode ganz sparen wenn man die Klasse die das Hauptfenster repräsentiert von `Tk` erben lässt, was ja allgemein das Hauptfenster ist und diese Methode bereits besitzt.
Das die `StrinVar`-Objekte mit dem Wert '1' initialisiert werden damit *keine* Auswahl bei den Radiobuttons angezeigt wird ist irreführend, denn wenn der Benutzer *nichts* auswählt, dann werden ja die Werte 'alphabetisch' und 'py' verwendet. Warum wird das dem Benutzer nicht angezeigt? Wenn in der GUI kein Radiobutton ausgewählt ist, dann würde ich als Benutzer ”unsortiert” und Dateien mit beliebiger Endung erwarten.
Hinter `set_auswahl` würde man eine „setter“-Methode erwarten, aber kein `StringVar`-Objekt. Zudem ist das eine Mischung aus Englisch und Deutsch innerhalb eines Namens. `vondate` und `bisdate` ebenfalls, und ohne Trennung zwischen den beiden Namen. Insgesamt sollte man bei *einer* Sprache bleiben um Verwirrungen zu vermeiden. `auswahl` ist auch zu generisch weil der Name nicht verrät *was* da ausgewählt wird — das Sortierkriterium.
`durchlaufe` klingt auch eher nach einer Tätigkeit und kaum jemand würde den Startpfad für die Suche hinter dem Namen vermuten.
`frame_li` und `frame_re` enthalten Abkürzungen und das für ”Ortsangaben” in der GUI die so nicht unbedingt sein müssen. Man könnte sich später auch entscheiden die anders darzustellen, zum Beispiel über- statt nebeneinander, dann stimmen die Namen nicht mehr. Man könnte beide auch einfach nur `frame` nennen, wenn man sie nicht ”auf Vorrat” am Anfang definieren würde, sondern erst wenn man sie benötigt. Oder man verwendet Namen die den Inhalt beschreiben wie `result_frame` und `controls_frame` oder etwas in der Art.
Bei `scrollbr` fehlt ein 'a' — und jetzt sag bitte nicht das das kein Versehen war, sondern das Du diesen einen Konsonanten absichtlich weggelassen hast.
`drei_buttons` und `sechs_buttons`? Da werden keine `Button`\s dran gebunden und die `drei_*` stimmt noch nicht einmal, denn das Wörterbuch hat vier Einträge. Ausser in der allerneuesten Python 3-Version ist ein Wörterbuch auch keine gute Idee weil die Werte dort keine feste Reihenfolge haben. Die Reihenfolge der Radiobuttons kann also bei jedem Programmstart anders sein. Eine Liste mit Tupeln wäre hier eine sinnvollere Wahl.
`key2` und `wert2` muss auch nicht sein, da kann man `key` und `wert` für schreiben. Nach welchem Kriterium wird entschieden welchen Namen man in Deutsch und welchen in Englisch verwendet? Und wonach ob man Zahlen oder Buchstaben anhängt (`radioba`/`radiobb`)?
`radioba.bind()` und `radiobb.bind()` beziehen sich jeweils nur auf den *letzten* der erzeugten Radiobuttons, das macht keinen Sinn.
Das Erzeugen der Radiobuttons passiert in zwei fast identischen Code-Blöcken. Code- und Datenwiederholung vermeidet man als Programmierer. Programme werden dadurch schwieriger zu warten und damit fehleranfälliger, denn wenn man etwas ändert, muss immer daran denken es in allen Kopien zu ändern und aufpassen das man es überall wirklich gleich ändert.
`uebernehme_auswahl()` sollte nicht lokal definiert werden. Die Funktion ist noch nicht einmal komplett ein Closure weil auf die Hälfte der Werte ”direkt” zugegriffen wird und auf die andere über `self`. Letztlich ist die Funktion aber auch komplett überflüssig, denn warum im Hintergrund Werte übernehmen, wenn man das auch erst dann machen kann wenn man sie *braucht*. Also `von` und `bis` auch an das Objekt binden, und die ganzen redundanten Attribute loswerden und in der `main()` erst alle Werte aus der GUI auslesen.
Der Name `main()` für eine Methode die nicht das Hauptprogramm enthält ist falsch.
Die `suche_files()` ist in gewisser Weise mit der GUI vermischt, denn sie bekommt nahezu ungeprüfte Nutzereingaben. Sie sollte Datumsobjekte bekommen und nicht vom Benutzer eingegebene Zeichenketten. Die Eingabe entgegen zu nehmen zu prüfen und zu konvertieren ist Aufgabe der GUI. Die Daten werden auch ständig aufs neue konvertiert für jeden Vergleich.
Beim Anzeigen des Ergebnisses ist der Name `dateien` falsch weil der nur an einen Dateinamen gebunden wird. Letztlich ist es auch nicht besonders effizient die Namen alle einzeln in das `Text`-Widget zu schreiben und es ist auch ziemlich verwirrend das die in umgekehrter Reihenfolge ausgegeben werden. Ich habe mich zuerst sehr gewundert warum `suche_files()` immer alles `reverse` sortiert bis ich das gesehen habe. Damit rechnet niemand und die Programmlogik sollte nichts über solche Details wissen müssen. Die Funktion formatiert auch die Ergebnisse als Zeichenketten — im Grunde ebenfalls Aufgabe der GUI.
Für die Sortierkriterien würde ich Konstanten für die Zeichenketten einführen, oder noch besser ein `enum.Enum` erstellen.
Das `file_dict` ist ein Fehler wenn nach Zeit sortiert wird, denn Du bildest dort Zeiten auf Dateinamen ab. Das funktioniert nur wenn alle Dateien garantiert unterschiedliche Zeiten haben, was so ganz und gar nicht garantiert ist. Es gibt ``touch`` oder Dateisysteme mit einer relativ niedrigen Zeitauflösung was die Metadaten angeht, da kann man ganz locker viele Dateien mit der gleichen Zeit haben. Es ist auch verwirrend das in manchen Fällen in diesem Wörterbuch Zeiten auf Namen abgebildet werden und in anderen Namen auf Zeiten. Die Ergebniswerte sollten immer die gleiche Reihenfolge haben.
`verzeichnisse` wird nur an *einen* Verzeichnispfad gebunden. Genau so falsch ist `dateinamen` in der inneren Schleife und später `filetimes`.
Es wird (zu) oft `os.path.join()` mit den gleichen Werten gemacht. Also sowohl der Code dafür als auch die tatsächliche Ausführung. Das macht man einmal am Anfang und gut ist.
Das filtern nach Anfangs-/Endzeit ist komisch gelöst. Erstens passiert es nur wenn nicht nach Name sortiert wird. Warum dann nicht? Nichts in der GUI deutet auf diese Einschränkung hin und ich finde das sehr überraschend. Dann müssen entweder beide Endpunkte eingegeben worden sein. Wird nur einer eingegeben, bleibt das Ergebnis einfach leer‽ Auch etwas unerwartet für meinen Geschmack. Man kann doch auch nur eine Grenze setzen wollen und das macht doch auch total Sinn nur nach Dateien zu suchen die älter als X sind oder jünger als Y.
Wenn man das generell für alle Sortierkriterien macht, dann bleibt in den ganzen ``if``/``elif``-Zweigen nur noch das ermitteln von `zeitpunkt` und es unterscheiden sich nur der Test auf die Sortierkonstante und die Funktion die zum ermitteln der Zeit verwendet wird. Das kann man ohne das ``if``/``elif``/… lösen mit einem Wörterbuch das die Sortierkonstanten auf die Funktionen abbildet. Schon hat man nur noch eine Stelle im Code wo Datei + Zeit zur Ergebnisliste hinzugefügt wird.
Anstelle von einer Zahl oder einer Zeichenkette wären Datums-/Zeitangaben besser als `datetime.datetime` repräsentiert. Die kann man auch vergleichen, die `repr()`-Ausgabe ist lesbarer, und man kann die `format()`-Methode verwenden.
So als zwischenergebnis lande ich erst einmal hier (kaum getestet):
Code: Alles auswählen
import os
from datetime import datetime as DateTime
from tkinter import (
Tk, Frame, Label, Button, Text, Scrollbar, Radiobutton, Entry,
StringVar, END, LEFT, RIGHT, Y, W
)
#
# TODO Maybe use `enum.Enum`.
#
SORT_BY_NAME = 'name'
SORT_BY_CREATION_TIME = 'creation_time'
SORT_BY_CHANGE_TIME = 'change_time'
SORT_BY_ACCESS_TIME = 'access_time'
SORT_BY_TIME = [SORT_BY_CREATION_TIME, SORT_BY_CHANGE_TIME, SORT_BY_ACCESS_TIME]
SORT_BY_TO_GET_TIME_FUNC = {
SORT_BY_NAME: os.path.getctime,
SORT_BY_CREATION_TIME: os.path.getctime,
SORT_BY_CHANGE_TIME: os.path.getmtime,
SORT_BY_ACCESS_TIME: os.path.getatime,
}
DATE_FORMAT = '%d.%m.%Y'
def search_files(start_path, sort_by, filename_extension, start_date, end_date):
#
# TODO Own class for the result tuples or at least a
# `collections.namedtuple`.
#
os.stat_float_times(False)
result = list()
for path, _, filenames in os.walk(start_path):
for filename in filenames:
full_path = os.path.join(path, filename)
if os.path.splitext(filename)[1] == filename_extension:
file_time = DateTime.fromtimestamp(
SORT_BY_TO_GET_TIME_FUNC[sort_by](full_path)
)
if (
(not start_date or start_date < file_time)
and (not end_date or file_time <= end_date)
):
result.append((full_path, file_time))
if sort_by == SORT_BY_NAME:
result.sort(key=lambda x: x[0].lower())
elif sort_by in SORT_BY_TIME:
result.sort(key=lambda x: x[1])
else:
raise ValueError('unknown sort criterion {0!r}'.format(sort_by))
return result
class Dateisuche(Tk):
def __init__(self):
Tk.__init__(self)
self.title('Dateisuche')
self['bg'] = 'gray50'
self.sort_criterion = StringVar(value=SORT_BY_NAME)
self.filename_extension = StringVar(value='.py')
self.start_path = '.'
#
# Linker Frame.
#
frame = Frame(self, bg='gray50', padx=2)
self.result_text = Text(frame, width=92, height=38)
self.result_text.pack(pady=0, padx=2)
scrollbar = Scrollbar(frame)
scrollbar.config(command=self.result_text.yview)
scrollbar.pack(side=RIGHT, fill=Y)
self.result_text.config(yscrollcommand=scrollbar.set)
frame.pack(side=LEFT)
#
# Rechter Frame.
#
self.background_color = '#6f6352'
self.font_name = 'cambria'
frame = Frame(self, bg=self.background_color, padx=2)
self._create_radiobuttons(
frame,
'Auswahl Sortierung',
self.sort_criterion,
[
('Alphabetisch', SORT_BY_NAME),
('Datei angelegt', SORT_BY_CREATION_TIME),
('Letzte Änderung', SORT_BY_CHANGE_TIME),
('Letzter Zugriff', SORT_BY_ACCESS_TIME),
],
)
self._create_radiobuttons(
frame,
'Auswahl Endung',
self.filename_extension,
[
('doc', '.doc'),
('docx', '.docx'),
('htm', '.htm'),
('html', '.html'),
('pdf', '.pdf'),
('py', '.py'),
],
)
self._create_headline(frame, 'Auswahl Zeitraum')
options = dict(
bg=self.background_color,
font=(self.font_name, 10),
fg='#ffce58',
justify=LEFT,
)
Label(
frame, text='Beispiel: 28.06.2017\n\nVon:', **options
).pack(padx=10, anchor=W)
self.start_date_entry = Entry(frame)
self.start_date_entry.pack(padx=10, anchor=W)
Label(frame, text='Bis:', **options).pack(padx=10, anchor=W)
self.end_date_entry = Entry(frame)
self.end_date_entry.pack(padx=10, anchor=W)
search_button = Button(
frame,
text='Suche',
font=(self.font_name, 10, 'bold'),
padx=18,
command=self.do_search,
)
search_button.pack(pady=18)
frame.pack(side=LEFT, fill=Y)
def _create_headline(self, parent, text):
Label(
parent,
text=text,
fg='#ffe9b3',
bg=self.background_color,
font=(self.font_name, 11),
justify=LEFT,
).pack(padx=10, pady=12, anchor=W)
def _create_radiobuttons(self, parent, headline_text, variable, choices):
self._create_headline(parent, headline_text)
for text, value in choices:
Radiobutton(
parent,
text=text,
fg='#ffce58',
bg=self.background_color,
selectcolor='#606060',
activebackground=self.background_color,
variable=variable,
value=value,
).pack(padx=8, anchor=W)
def do_search(self):
try:
start_date = DateTime.strptime(
self.start_date_entry.get(), DATE_FORMAT
)
except ValueError:
start_date = None
try:
end_date = DateTime.strptime(self.end_date_entry.get(), DATE_FORMAT)
except ValueError:
end_date = None
paths = search_files(
self.start_path,
self.sort_criterion.get(),
self.filename_extension.get(),
start_date,
end_date,
)
self.result_text.delete(1.0, END)
self.result_text.insert(
1.0, '\n'.join(map('{0[0]} - {0[1]:%d.%m.%Y}'.format, paths))
)
self.result_text.insert(END, '\n')
def main():
Dateisuche().mainloop()
if __name__ == '__main__':
main()