Ordnerstrukturen auf Aktualisierung prüfen

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.
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

den os.stat-Fehler hatte ich, nachdem ich in meiner Logik das set mit dem stat (Path().stat().st_ctime) als Vorschlag von ElektroBerry kombinieren wollte.
Denn wenn sich die Zeit einer Datei ändert und diese mit dem ersten erstellten set verglichen wird, müsste dort ja die Änderung eintreten (diie Datei wurde aktualisiert?).

Das sich die Änderungen immer ändern liegt ja daran, dass ich das ganze nach und nach aufbaue und immer wieder teste, siehe die Meldung, dass eine Datei gelöscht wurde.
Nun ist die Frage, wenn eine aktuellere Dateio mit selben Namen kommt.

Das ich das ganze in einer while True-Schleife gebaut habe, kam ja nach dem Vorschlag von ElektroBerry. Ob ich den Durchlauf zähle oder nicht, dient hier nur zum erkennen, ob die Schleife wirklich läuft.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast ja schon einen stat-Aufruf von Path. Da macht es natürlich keinen Sinn, damit auch noch os.stat aufzurufen.
Und wie schon geschrieben, wenn man eine endlos-Schleife braucht nimmt man eine while-Schleife, wenn man aber einen Zähler braucht, ist halt eine for-Schleife besser.
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Okay, dann ist dies quasi Gesetz mit der while und for-Schleife, obwohl sie für den endlos Fall gleiches tun, danke.

Den stat-Aufruf habe ich ja bei dem set getestet, ohne Erfolg.

Beispeilsweise wäre ja hier nach dann der Fall, was passieren soll, wenn etwas "aktualisiert" wurde.

Code: Alles auswählen

 for filename in previous_text_files and text_files:
demnach würde ich hier hinter eine if-Bedingung mit einer Zeitabfrage der Datei einbauen müssen, weshalb ich wieder mit os.stat für die Datei arbeiten müsste, iwie sowas:

Code: Alles auswählen

os.stat(filename).st_atime
Das funktioniert so aber nicht, da ich keine weitere Abfrage einbaue, erweitere ich die Abfrage zu:

Code: Alles auswählen

if os.stat(filename in previous_text_files).st_atime != os.stat(filename in text_files).st_atime:
            print(f"{filename} aktualisiert")
erhalte ich dauerhaft die Meldung, dass die Datei aktualisiert wurde.

Ich habe das ganze versucht in deinen Code miteinzubauen:

Code: Alles auswählen

from pathlib import Path
from time import sleep
from itertools import count

FOLDER = Path('S:\Palkovits\intern\HPLC-Daten\Jens Heller')

print("Erster Scan")
previous_text_files = set(FOLDER.rglob("*.txt"))

for i in count(1):
    print(os.stat(filename).st_atime)
    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 and text_files:
        if os.stat(filename in previous_text_files).st_atime != os.stat(filename in text_files).st_atime:
            print(f"{filename} aktualisiert")
    previous_text_files = text_files
    print("Lauf", i)
    sleep(5)
Wo liegt denn mein Denkfehler? oder kenne ich erfahrungsmäßig gewisse Methoden nicht?
Muss ich vllt nach der Abfrage bei gleichen Sets die Datei öffnen und Zeile für Zeile vergleichen lassen?
Falls ja, wie baue ich diese Variante für n Dateien um, was ich bei Goggle fand, war nur zu konkreten Dateien.

Danke für deine Hilfe
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Karlirex hat geschrieben: Mittwoch 31. März 2021, 08:12 Okay, dann ist dies quasi Gesetz mit der while und for-Schleife, obwohl sie für den endlos Fall gleiches tun, danke.
Sie tun eben nicht das selbe. Im einen Fall hast Du einen Zähler, im anderen nicht.
Karlirex hat geschrieben: Mittwoch 31. März 2021, 08:12

Code: Alles auswählen

 for filename in previous_text_files and text_files:
Du kannst nicht Syntax raten und hoffen, dass es schon das macht, was Du hoffst. Hast Du Dir schonmal angeschaut, was der Ausdruck `previous_text_files and text_files` macht??

Genauso `filename in previous_text_files`! Was erwartest Du denn, was dieser Ausdruck innerhalb von os.stat macht? Und hast Du geprüft, was er wirklich macht?

Code baut man Stück für Stück auf, und testet jeden Schritt.
Also Dateien, die früher schon da waren und jetzt immer noch da sind, bekommt man über die Sets:

Code: Alles auswählen

kept_text_files = previous_text_files & text_files
Jetzt hast Du ein Set mit Dateinamen. Aber wie sollst Du daraus die Änderungszeit bestimmen?
Gar nicht. Deshalb mußt Du die Zeiten schon vorher bestimmen. Also neben dem Dateinamen auch die Änderungszeit speichern. Dazu bietet sich ein Wörterbuch an.

Code: Alles auswählen

def get_files_with_atime(folder):
    return {
        filename: filename.stat().st_atime
        for filename in folder.rglob("*.txt")
    }
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Aber den Fall "kept_text_files", welchen du beschreibst, löst du doch genauso über das & (and), wie ich, nur dass du dem ganzen einen neuen Namen (kept_text_files) gibst. Also warum diese Änderung?
Auch wenn while/for etwas anderes darstellen, nach der Konvention, schreibe ich doch nicht 3x Code, nur damit ich dazwischen sehe, ob er wirklich durchläuft, sondern nehme dann nur die letzten Punkte aus der while-Schleife.

Den Fall, welche du mit der Funktion beschrieben hast, gibt doch erstmal den gesamten Ordner als Zeit aus, in einem Wörterbuch gespeichert, oder?

Ich gebe mich mit der ganzen Materie etwas schwer, dass mag sein. Leider komme ich aus keiner Programmierecke und erhalte halt die ein oder andere Idee und versuche mich dann daran. Wenn ich mir verschiedene Bücher dazu ansehe, fühle ich mich leider einfach nur erschlagen, gerade, weil es für eine Lösung eben 7 Wege gibt (siehe unser Schleifenthema).
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Es macht einen Unterschied ob man `and` oder `&` schreibt. Und natürlich schreibst Du Code immer wieder neu und um. Auch mehr als drei mal.
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

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:

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()))
Mein gesamtes Skript sieht nun so aus:

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)

Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

`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.

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)
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

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
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Hallo,
ich erwecke den Thread nochmal zum Leben :D
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("*")
    }
Um es zu beschleunigen habe ich dann multiprocessing versucht:

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()
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
Benutzeravatar
__blackjack__
User
Beiträge: 13114
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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

__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.
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.
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
Benutzeravatar
__blackjack__
User
Beiträge: 13114
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
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

__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.
Alles klar.

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()
dabei wird eine Zeitverbesserung von 8s erzielt. mit einem apply_async / map_async /mehr Workern erhalte ich keinen weiteren Zeitschub. Schade.
Benutzeravatar
__blackjack__
User
Beiträge: 13114
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
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

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.
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

__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.
Mit chunksize=1 und lediglich einem (den größten Ordner) lande ich bei 418s statt 390s :D


Den Vorschlag mit scandir, versuche ich mal anzugehen.
Benutzeravatar
__blackjack__
User
Beiträge: 13114
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
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

__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.
Naja allein der größte Ordner dauert mit Pool und auch den anderen Ideen besagt 400s.

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)
hier die Idee mit walk, das dauert 112s für dazu noch alle anderen Ordner. Ich muss jetzt nur noch herausfinden, wie ich passend an die stats komme und das dann in ein Set so wie zuvor packen.
Mit einem drittel der Zeit kann ich mich nämlcih zu frieden stellen.
Antworten