Ordnerstrukturen auf Aktualisierung prüfen
Ich habe mich mit dem Problem der Aktualisierung nun einige Zeit beschäftigt, versucht dictionarys, sets, list und dataframes mit einander zu vergleichen, um jeweils den Unterschied im Zeitstempel zu erkennen. Leider ohne Erfolg.
Ich bekomme an der Stelle die ganze Zeit das eine print-Ausgabe, welche erstmal nicht da stehen sollte, bis eben eine Datei mit selben Namen aber unterschiedlicher Zeit in den Ordner gefügt wird.
Ich glaube ich übersehe ein Stück an dieser Stelle.
Das Stück Code für die Abfrage nach der letzten accesstime sieht so aus:
Mein gesamtes Skript sieht nun so aus:
Ich bekomme an der Stelle die ganze Zeit das eine print-Ausgabe, welche erstmal nicht da stehen sollte, bis eben eine Datei mit selben Namen aber unterschiedlicher Zeit in den Ordner gefügt wird.
Ich glaube ich übersehe ein Stück an dieser Stelle.
Das Stück Code für die Abfrage nach der letzten accesstime sieht so aus:
Code: Alles auswählen
for filename in previous_text_files & text_files:
with filename.stat().st_atime as filename:
if filename not in file_time:
print("neu")
dict1 = {filename: filename.stat().st_atime}
dict2 = dict(set(dict1.items()) - set(file_time.items()))
Code: Alles auswählen
import os
from pathlib import Path
from time import sleep
import time
FOLDER = Path(r'......')
print("Erster Scan")
previous_text_files = set(FOLDER.rglob("*.txt"))
i = 0
def get_file_time(FOLDER):
return {
filename: filename.stat().st_atime
for filename in FOLDER.rglob("*.txt")
}
file_time = get_file_time(FOLDER)
while True:
file_time = get_file_time(FOLDER)
text_files = set(FOLDER.rglob("*.txt"))
for filename in text_files - previous_text_files:
print(f"{filename} ist neu")
for filename in previous_text_files - text_files:
print(f"{filename} gelöscht")
for filename in previous_text_files & text_files:
with filename.stat().st_atime as filename:
if filename not in file_time:
print("neu")
dict1 = {filename: filename.stat().st_atime}
dict2 = dict(set(dict1.items()) - set(file_time.items()))
if not text_files:
print("Keine Daten enthalten")
previous_text_files = text_files
i = i + 1
print("Lauf", i)
sleep(5)
`os` und `time` werden importiert aber nicht benutzt.
FOLDER in `get_file_time` ist keine Konstante, muß also `folder` geschrieben werden.
Du suchst alle Dateien immer zwei mal, einmal mit Änderungszeit und einmal ohne. Dabei ist zweiteres völlig unnötig.
Bei dem with-Statement bekommst Du einen Fehler, wäre gut gewesen, wenn Du den hier auch posten würdest. Was hoffst Du, bewirkt with hier?
Würde das with funktionieren, wäre in `filename` die Änderungszeit gespeichert und die wäre niemals ein Schlüssel in `file_time`. Für was soll `dict1` und `dict2` gut sein?
Und wieder hast Du eine while-Schleife, die eine for-Schleife sein könnte.
FOLDER in `get_file_time` ist keine Konstante, muß also `folder` geschrieben werden.
Du suchst alle Dateien immer zwei mal, einmal mit Änderungszeit und einmal ohne. Dabei ist zweiteres völlig unnötig.
Bei dem with-Statement bekommst Du einen Fehler, wäre gut gewesen, wenn Du den hier auch posten würdest. Was hoffst Du, bewirkt with hier?
Würde das with funktionieren, wäre in `filename` die Änderungszeit gespeichert und die wäre niemals ein Schlüssel in `file_time`. Für was soll `dict1` und `dict2` gut sein?
Und wieder hast Du eine while-Schleife, die eine for-Schleife sein könnte.
Code: Alles auswählen
from pathlib import Path
from time import sleep
FOLDER = Path(r'......')
def get_filenames_with_accesstime(folder):
return {
filename: filename.stat().st_atime
for filename in FOLDER.rglob("*.txt")
}
print("Erster Scan")
previous_text_files = get_filenames_with_accesstime(FOLDER)
for i in count(1):
print("Lauf", i)
text_files = get_filenames_with_accesstime(FOLDER)
for filename in set(text_files) - set(previous_text_files):
print(f"{filename} ist neu")
for filename in set(previous_text_files) - set(text_files):
print(f"{filename} gelöscht")
for filename in set(previous_text_files) & set(text_files):
if previous_text_files[filename] != text_files[filename]:
print(f"{filename} geändert")
if not text_files:
print("Keine Daten enthalten")
previous_text_files = text_files
sleep(5)
Also das with war die Idee, damit die Zeitstempel abzufragen, da ich so schon Programme gesehen habe, die Ordner nur dann öffnen, wenn dort bpsw auch ein with Argument erfüllt ist.
Das dict war die Überlegung die Zeiten so speichern und vergleichen zukönnen.
Wenn ich deinen Code nun so übernehme, laufe ich zwar in keinerlei Fehler, dennoch läuft das Programm an der Schleife mit der Aktualisierung scheinbar vorbei. Denn ich erhalte keine Meldung, wenn ich die neue Datei mit dem geänderten Zugriff in den Ordner überschreibe.
Kannst Du mir eventuell deine if-Abfrage genauer erklären, also warum nun da das [filename] enthalten ist?
Vielleicht finde ich dann auch dort den letzten Fehler, damit das Programm passend läuft.
Vielen Dank, beste Grüße
Das dict war die Überlegung die Zeiten so speichern und vergleichen zukönnen.
Wenn ich deinen Code nun so übernehme, laufe ich zwar in keinerlei Fehler, dennoch läuft das Programm an der Schleife mit der Aktualisierung scheinbar vorbei. Denn ich erhalte keine Meldung, wenn ich die neue Datei mit dem geänderten Zugriff in den Ordner überschreibe.
Kannst Du mir eventuell deine if-Abfrage genauer erklären, also warum nun da das [filename] enthalten ist?
Vielleicht finde ich dann auch dort den letzten Fehler, damit das Programm passend läuft.
Vielen Dank, beste Grüße
Was Wörterbücher sind, und wie man sie verwendet, gehört zu den absoluten Grundlagen.
Wann atime, mtime oder ctime geändert werden, hängt vom Filesystem ab. Wie das unter Windows gehandhabt wird, kann ich gerade nicht sagen. Aber wenn Du Dateien änderst, interessiert dich eher mtime.
Wann atime, mtime oder ctime geändert werden, hängt vom Filesystem ab. Wie das unter Windows gehandhabt wird, kann ich gerade nicht sagen. Aber wenn Du Dateien änderst, interessiert dich eher mtime.
Hallo,
ich erwecke den Thread nochmal zum Leben
Meine Ordner sind mit der Zeit deutlich größer geworden.
Dabei habe ich längere Laufzeiten der Funktion festegestellt (aktuell 6min+).
Um es zu beschleunigen habe ich dann multiprocessing versucht:
das ist aber auch nur 0,4s schneller.
Gibts es eine andere Möglichkeit die Funktion zu erweitern, habe ich etwas falsch gemacht oder ist aufgrund der Datenmenge das hier einfach limitiert?
Danke für eure Hilfe und Grüße
Karlirex
ich erwecke den Thread nochmal zum Leben
Meine Ordner sind mit der Zeit deutlich größer geworden.
Dabei habe ich längere Laufzeiten der Funktion festegestellt (aktuell 6min+).
Code: Alles auswählen
def get_filenames_with_accesstime(folder):
return {
filename: filename.stat().st_atime
for filename in FOLDER.rglob("*")
}
Code: Alles auswählen
def my_func(FOLDER, queue):
queue.put({
filename: filename.stat().st_mtime
for filename in FOLDER.rglob("*")
})
queue = multiprocessing.Queue()
previous_text_files = dict()
start = timeit.default_timer()
for i in path_list:
p1 = multiprocessing.Process(target=my_func, args=(Path(i), queue))
p1.start()
previous_text_files.update(queue.get())
p1.join()
Gibts es eine andere Möglichkeit die Funktion zu erweitern, habe ich etwas falsch gemacht oder ist aufgrund der Datenmenge das hier einfach limitiert?
Danke für eure Hilfe und Grüße
Karlirex
- __blackjack__
- User
- Beiträge: 13117
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Karlirex: Da ist *gar kein* Gewinn zu erwarten, weil da immer nur *ein* zusätzlicher Prozess läuft, während der Hauptprozess auf den *wartet* und nichts tut. Ich würde da erst einmal schauen ob ein `Pool` und `imap_unordered()`, `map()`, oder die `*_async()`-Methoden verwendet werden kann, bevor ich mir da selbst was zur Übertragung von Daten bastele und die Funktion ändern muss, die das ganze ausführt. Selbst dann wären da noch die `*_async()`-Methoden von `Pool`.
Ich wette es wurde schon mal was zu Namen gesagt. `my_*` ist ein sinnloser Präfix, der in 99,9999% nichts aussagt und da nicht hingehört. `FOLDER` wäre eine Konstante, Argumentnamen sind aber nie Konstanten. `i` ist als Name für etwas anderes als ganze Zahlen falsch. Niemand kommt darauf, das es sich in diesem Fall um einen Ordnerpfadnamen handelt. `filename` ist falsch, weil an den Namen nicht nur Dateinamen (eigentlich ja *Pfade*), sondern auch Ordnerpfade gebunden werden. Daraus folgt, dass auch `get_filenames_with_accesstime()` als Name falsch ist.
Ich wette es wurde schon mal was zu Namen gesagt. `my_*` ist ein sinnloser Präfix, der in 99,9999% nichts aussagt und da nicht hingehört. `FOLDER` wäre eine Konstante, Argumentnamen sind aber nie Konstanten. `i` ist als Name für etwas anderes als ganze Zahlen falsch. Niemand kommt darauf, das es sich in diesem Fall um einen Ordnerpfadnamen handelt. `filename` ist falsch, weil an den Namen nicht nur Dateinamen (eigentlich ja *Pfade*), sondern auch Ordnerpfade gebunden werden. Daraus folgt, dass auch `get_filenames_with_accesstime()` als Name falsch ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Zu my gabs schon Kommentare ja, zum reinen Testen bei Problemen benenne ich es aber "schnell/einfach" oder übernehme von Stackoverflow direkt um zu schauen ob sich was am Problem ändert. Tut sich das, werden die Namen angepasst.__blackjack__ hat geschrieben: ↑Montag 12. Dezember 2022, 11:12 Ich wette es wurde schon mal was zu Namen gesagt. `my_*` ist ein sinnloser Präfix, der in 99,9999% nichts aussagt und da nicht hingehört. `FOLDER` wäre eine Konstante, Argumentnamen sind aber nie Konstanten. `i` ist als Name für etwas anderes als ganze Zahlen falsch. Niemand kommt darauf, das es sich in diesem Fall um einen Ordnerpfadnamen handelt. `filename` ist falsch, weil an den Namen nicht nur Dateinamen (eigentlich ja *Pfade*), sondern auch Ordnerpfade gebunden werden. Daraus folgt, dass auch `get_filenames_with_accesstime()` als Name falsch ist.
Zur der Sache mit FOLDER, filename, get_filenames_with_accesstime() finde ich ganz interessant, da ich diese Bezeichnung von Sirius übernommen habe und ihr beide ja sehr darauf achtet, dass immer alles korrekt benannt ist, dies auch hier dann doch "falsch" ist, siehe zwei drei Postings vorher von ihm.
Grüße
- __blackjack__
- User
- Beiträge: 13117
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Karlirex: Zumindest `FOLDER` war korrekt, weil das dort noch eine Konstante war, als das noch so hiess. Ich mache in der Regel einen Unterschied in der Benennung das `Path`-Objekte auch wirklich `path` im Namen haben um bei `file_path` vs. `filename` zu wissen, dass das eine ein Pfad-Objekt (zu einer Datei) ist, und das andere eine Zeichenkette mit einem Dateinamen. Insbesondere in Programmen/Programmteilen wo beide Varianten vorkommen können, vermeidet das Verwirrung.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Alles klar.__blackjack__ hat geschrieben: ↑Montag 12. Dezember 2022, 13:01 @Karlirex: Zumindest `FOLDER` war korrekt, weil das dort noch eine Konstante war, als das noch so hiess. Ich mache in der Regel einen Unterschied in der Benennung das `Path`-Objekte auch wirklich `path` im Namen haben um bei `file_path` vs. `filename` zu wissen, dass das eine ein Pfad-Objekt (zu einer Datei) ist, und das andere eine Zeichenkette mit einem Dateinamen. Insbesondere in Programmen/Programmteilen wo beide Varianten vorkommen können, vermeidet das Verwirrung.
Ich habe mal nach dem Tutorial von Pool gearbeitet und folgendes probiert:
Code: Alles auswählen
from multiprocessing import Pool
process = Pool(5)
with process:
start = timeit.default_timer()
results = process.map(get_filenames_with_accesstime, path_list)
end = timeit.default_timer()
- __blackjack__
- User
- Beiträge: 13117
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Ich würde hier wahrscheinlich noch die `chunksize` manuell auf 1 setzen sofern in `path_list` nicht viele Werte vorkommen die nur ganz wenige Ergebnisse liefern.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Ich hätte noch den Vorschlag, mit os.scandir() zu arbeiten. Nach meiner Erfahrung bzw. Erinnerung ist das schneller, wenn die Filesystem-Zugriffe der Flaschenhals sind. Die DirEntry-Objekte, die man von os.scandir() bekommt, ersparen den zusätzlichen stat()-Aufruf, weil sie die Werte gleich einlesen und cachen. Man muss also nur einmal ans Filesystem ran, und auch nicht für jede Datei einzeln.
Soweit mein Verständnis, bin kein Dateisystem-Experte.
Soweit mein Verständnis, bin kein Dateisystem-Experte.
Mit chunksize=1 und lediglich einem (den größten Ordner) lande ich bei 418s statt 390s__blackjack__ hat geschrieben: ↑Montag 12. Dezember 2022, 14:11 Ich würde hier wahrscheinlich noch die `chunksize` manuell auf 1 setzen sofern in `path_list` nicht viele Werte vorkommen die nur ganz wenige Ergebnisse liefern.
Den Vorschlag mit scandir, versuche ich mal anzugehen.
- __blackjack__
- User
- Beiträge: 13117
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Karlirex: Das dann nur mit *einem* Ordner zu machen ist ja ein bisschen witzlos, denn da wird dann ja garantiert nichts parallel ausgeführt, und man hat nur den zusätzlichen Overhead das die Daten zwischen den Prozessen kommuniziert werden müssen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Naja allein der größte Ordner dauert mit Pool und auch den anderen Ideen besagt 400s.__blackjack__ hat geschrieben: ↑Dienstag 13. Dezember 2022, 08:47 @Karlirex: Das dann nur mit *einem* Ordner zu machen ist ja ein bisschen witzlos, denn da wird dann ja garantiert nichts parallel ausgeführt, und man hat nur den zusätzlichen Overhead das die Daten zwischen den Prozessen kommuniziert werden müssen.
Code: Alles auswählen
start = timeit.default_timer()
#for path in path_list:
for root, dirs, files in os.walk(path_list):
for filename in files:
file_path = os.path.join(root, filename)
end = timeit.default_timer()
print("Zeit für einen Durchlauf: ", end-start)
Mit einem drittel der Zeit kann ich mich nämlcih zu frieden stellen.
@Karlirex: sehr gut, Du hast herausgefunden, dass der `stat` Aufruf zwei Drittel der Zeit verbrät. Das hat jetzt nichts mit Deinem Rückschritt von pathlib auf os zu tun.
Willst Du auf Geschwindigkeit optimieren, mußt Du Dich in die low-level-API von os.scandir vertiefen, wie das bords0 ja schon vorgeschlagen hatte.
Willst Du auf Geschwindigkeit optimieren, mußt Du Dich in die low-level-API von os.scandir vertiefen, wie das bords0 ja schon vorgeschlagen hatte.
Vielleicht ist der Ansatz, das Filesystem auf Zeitänderungen Änderungen zu scannen, einfach nicht ideal.
Jedes modernes Betriebssystem hat eine Kernel Schnittstelle, welche Events senden kann, wenn sich Dateien ändern.
Unter Linux wäre das zum Beispiel inotify unter Windows gibt es den FileSystemWatcher.
Das Pythonmodul watchdog https://pythonhosted.org/watchdog/ behauptet, die alle hinter einer API zu kapseln.
Vielleicht ist es Erfolgs versprechender, das Module zu benutzen und sich auf die gewünschten Events zu registrieren, statt selbst das Filesystem periodisch zu scannen.
Jedes modernes Betriebssystem hat eine Kernel Schnittstelle, welche Events senden kann, wenn sich Dateien ändern.
Unter Linux wäre das zum Beispiel inotify unter Windows gibt es den FileSystemWatcher.
Das Pythonmodul watchdog https://pythonhosted.org/watchdog/ behauptet, die alle hinter einer API zu kapseln.
Vielleicht ist es Erfolgs versprechender, das Module zu benutzen und sich auf die gewünschten Events zu registrieren, statt selbst das Filesystem periodisch zu scannen.
- __blackjack__
- User
- Beiträge: 13117
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@imonbln: Kommt auf den Einsatzzweck an. Wenn ich einmal im Monat eine inkrementelle Sicherung machen will, dann ist es eher nicht so sinnvoll den ganzen Monat lang ein Programm laufen zu haben, das hoffentlich alle Änderungen mitbekommt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
- DeaD_EyE
- User
- Beiträge: 1021
- Registriert: Sonntag 19. September 2010, 13:45
- Wohnort: Hagen
- Kontaktdaten:
Da es mich mal interessiert hat, wie schlecht PyInotify funktioniert, habe ich das mal mit asyncio umgesetzt:
Wenn mehrere Dateien verschoben werden und die Events nicht nacheinander kommen, funktioniert das nicht. Jedenfalls konnte ich das nicht reproduzieren. Möglicherweise kann so ein Szenario aber auftreten.
Code: Alles auswählen
import asyncio
from pyinotify import (
IN_CLOSE_WRITE,
IN_DELETE,
IN_MOVED_FROM,
IN_MOVED_TO,
AsyncioNotifier,
Event,
WatchManager,
)
class FSEvent:
def __init__(
self,
directory,
*,
recursive=True,
loop=None,
create_cb=None,
delete_cb=None,
moved_cb=None,
):
self.loop = loop or asyncio.get_running_loop()
self.create_cb = create_cb
self.delete_cb = delete_cb
self.moved_cb = moved_cb
self._wm = WatchManager()
self._notifier = AsyncioNotifier(
self._wm, self.loop, default_proc_fun=self.handler
)
self.last_cookie_event = None
events = IN_CLOSE_WRITE | IN_MOVED_TO | IN_MOVED_FROM | IN_DELETE
self._wm.add_watch(directory, events, rec=recursive)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_obj, exc_tb):
self._notifier.stop()
def handler(self, event: Event):
if event.mask & IN_MOVED_FROM:
self.last_cookie_event = event
elif event.mask & IN_MOVED_TO and event.cookie == self.last_cookie_event.cookie:
if callable(self.moved_cb):
asyncio.create_task(self.moved_cb(self.last_cookie_event, event))
self.last_cookie_event = None
elif event.mask & IN_CLOSE_WRITE:
if callable(self.create_cb):
asyncio.create_task(self.create_cb(event))
elif event.mask & IN_DELETE:
if callable(self.delete_cb):
asyncio.create_task(self.delete_cb(event))
async def create_cb(event: Event):
print(event.pathname, "geschrieben und geschlossen")
async def delete_cb(event: Event):
print(event.pathname, "gelöscht")
async def moved_cb(event_from: Event, event_to: Event):
print(f"{event_from.pathname} nach {event_to.pathname} verschoben")
async def main():
watcher = FSEvent(
"/home/andre/Downloads",
create_cb=create_cb,
delete_cb=delete_cb,
moved_cb=moved_cb,
)
# verhindert, dass main beendet wird
while True:
await asyncio.sleep(10)
if __name__ == "__main__":
#loop = asyncio.new_event_loop()
#loop.run_until_complete(main())
#loop.run_forever()
# man soll ja asyncio.run nutzen
asyncio.run(main())
# Ausgabe:
# [andre@andre-Fujitsu-i5 ~]$ python observer.py
# /home/andre/Downloads/test geschrieben und geschlossen
# /home/andre/Downloads/test nach /home/andre/Downloads/test2 verschoben
# /home/andre/Downloads/test2 nach /home/andre/Downloads/test verschoben
# /home/andre/Downloads/test gelöscht
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Code: Alles auswählen
complete_dict_with_time = dict()
for path in path_list:
for root, dirs, files in os.walk(path):
for filename in files:
file_path = os.path.join(root, filename)
time = os.stat(file_path).st_mtime
complete_dict_with_time[os.path.join(root, filename)] = os.stat(file_path).st_mtime
Das Suchintervall sollte recht flott sein, da die Idee dann läuft, sobald mit geänderten/neuen Daten weiterzuverarbeiten.
*muss aber, sollte ich beide Sets wie vorher verwenden, zweimal durchlaufen bevor der Setvergleich kommt. (Also 20min für einen Vergleich, was mMn. doch wieder recht lang ist).
Mit dem Watchdog-Package habe ich mich mal befasst, bin aber nicht sonderlich weit gekommen/warm geworden. Wahrscheinlich, weil ich da keine weitere Nutzung der Dateien habe?
Vllt schau ich mir das aber nochmal an.