Soll Funktion sich selbst aufrufen?

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
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

Hallo,
ich bin neu im Forum, Python Programmieranfänger und habe eine Frage:
Ich bin grade dabei ein Script zu schreiben (einfach nur zum Spaß) für eine kleine verbesserung von shutil.copyfile / shutil.copytree.
Das Script, nimmt 2 parameret an scr und dst (und dann noch ein paar **kwargs). Bei dem skript geht es darum, falls der Ordner schon exestiert (dst), wird dst umbenannt zu dst+'copy' und scr wird in dst kopiert. Danach kopiert das programm alles aus dst+'copy' in dst und guckt, ob eine Datei oder ein Ordner schon exestiert. Bei einer Datei, die 2 mal exestier, wird die as dst genommen, aber beim Ordner wollte ich es so machen, dass alles aus dem Ordner in den anderen Ordner kopiert wird. Dafür habe ich dann meine eigene Funktion aufgerufen und wollte nun fragen, ob es unschön ist, wenn eine Funktion sich selber Aufruft? Denn nach 996 aufrufen kriegt man ja einen Fehler, und mann sollte jegliche Fehler ja vermeiden: d.h wenn es 997 Unterordner gibt, kommt ein Fehler. Aber dann als gegenfrage: wie sollte ich das anders machen? Weil ich ja dann noch immer in meiner funktion bin, und ich die Variablen schlecht ver#ndern kann... sollte ich das dann mit listen oder dictonarys machen?
Danke für alle Antworten im voraus :D
Benutzeravatar
noisefloor
User
Beiträge: 4157
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Python ist in Rekursion nicht wirklich gut bzw. es gibt bei Python keinerlei Optimierungen bzgl. Rekursion und das Default Rekursionslimit ist 1000 (kann man aber ändern).

Python setzt auf Iteration und es gibt ja auch diverse Möglichkeiten, sich so rekursiv durch alle Unterverzeichnisse zu hangeln. Eine IMHO ganz brauchbare Übersicht gibt es hier: https://www.newbedev.com/python/howto/h ... directory/

Gruß, noisefloor
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Zeig doch Deinen Code. So ganz kann ich nicht folgen, warum das Rekursionslimit problematisch sein soll, da Du ja kaum 999 verschachtelte Unterunterunterverzeichnisse hast.
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das Rekursionslimit kann man zwar ändern, aber auch nicht einfach beliebig hoch setzen. Das heisst man kann da zwar schon nahezu beliebige Werte setzen, aber dann gibt es irgendwann keine saubere Ausnahme mehr, sondern das Programm stürzt einfach hart ab, wenn der Speicherbereich ”überläuft”, den das System für den Stack des Prozesses vorgesehen hat.

Ich würde die Finger von dieser Einstellung lassen. Das ist etwas für ”Notfälle”. Beispielsweise wenn man gerade zu dem Zeitpunkt einen Programmlauf braucht und erst hinterher Zeit hat das so umzuschreiben, dass das Limit nicht mehr erreicht wird.

@Traver: An der Stelle musst Du entscheiden ob 900+ Unterverzeichnisse in der Tiefe tatsächlich ein relevanter Fall ist. Ich würde das spontan verneinen und behaupten da hätten sicher auch andere Funktionen und Programme Probleme mit.

Ansonsten müsste man halt die Rekursion in eine Iteration umwandeln, in dem man den impliziten Aufrufstapel durch einen expliziten, selbst geschriebenen Stapel ersetzt.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

Danke erstmal für die Antworten, ihr habt mir weitergeholfen :)
Ich denke, ich probiere es ohne Rekursion, und verwandele es lieber in eine Iteration wie es _blackjack_ gesagt hat. Es ist
1: auf jeden Fall fehlerfreier (auch wenn ich warscheinlich niemals 996 Unterverzeichnisse haben werde) und es auf keinen fall ein "Notfall" ist
2: auch einfacher zu verstehen, denn ich hab mitlerweile den überblick über mein Programm verloren, weil ich nicht mehr genau verstanden habt, wie ich jetzt meine eigene Funktion aufrufe, und welche Parameter ich ihr übergebe usw... :oops:
@Sirius3 : ich hatte keine 996 Unterverzeichnisse, habe einfach nur mal damit mit einem anderen Script rumgespielt, weil ich wissen wollte was passiert.
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Traver: Ob das traversieren einer rekursiven Datenstruktur mit einer iterativen Lösung, die mehr Aufwand im Code betreiben muss, jetzt tatsächlich einfacher zu verstehen ist…
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

@_blackjack_ : Denke schon, da ich ja andernfalls immer wieder in die Funktion reingehen würde, und ich dann total den Durchblick verlieren würde/verloren hab... wenn ich es irgendwie mit einer schleife löse, finde ich es übersichtlicher, als 5 mal die Funktion aufzurufen und dann den Totalüberblick zu verlieren (nicht mal mehr richtig wissen, welcher Parameter grade was macht oder welches File oder dir jetzt schon kopiert wurde oder wo der Fehler ist). Da finde ich ein Dictonary, welches alle alle Verzeichnisse auflistet mit ihr Unterverzeichnissen usw... doch viel übersichtlicher oder? Change my mind, bin erst Anfänger, ab wie behält man bei der zichsten Rekrusion den überblick, wenn ein Fehler passiert, und man wirklich gar keine Ahnung mehr hat wo der Fehler ist oder was der Fehler ist?
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Da gibt es nicht wirklich einen Unterschied. Du mußt ja die selbe Daten durcharbeiten und eine iterative Lösung braucht meist noch zusätzlich Datenstrukturen, um sich zu merken, wo man sich gerade befindet.
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

@Sirius3: Aber ich hatte schon Probleme, dass manchmal sich der Ordner doppelt kopiert hat, und ich dann angefangen hab, meine **kwargs nach Lust und Laune zu verändern(d.h ich hab einfach rumgeraten ohne Plan, weil ich den überblick über die ganze Funktion verloren hatte, weil so viele Variablen sich verändert haben, und ich fast ihre Funktion vergessen hatte). Das ist halt gar nicht mein Style, ich muss alles immer ganz genau wissen wie alles funktioniert, deswegen bin ich jetzt dabei, alles noch mal neu zu machen. Ist vllt. nur meine Meinung, ich finde es (grade für Python Anfänger wie mich) übersichtlicher, mit Iterationen zu programmieren, anstatt mit Rekrusionen.
Und ich finde die zusätzlichen Datenstrukturen ganz gut, weil man sie individuell verändern kann und nicht direkt für die komplette Funktion.
Jetzt als letzte Frage:
soll ich nun doch Rekrusionen machen, oder lieber mit Iterationen arbeiten und alles Idiontensicher machen, falls mal einer doch 996 unterfiles hat? In meiner Python-PDF (mit der ich Python lerne) steht, dass man immer ALLES idiotensicher machen soll... stimmt dass dann doch nicht oder wie?
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Meist finden das Anfänger genau andersrum, wenn das Problem eine schöne Rekursion zuläßt. So ganz habe ich aber noch nicht verstanden, wie Du kopierst. Vielleicht liegt es einfach daran, dass Du Rekursion falsch einsetzt. Aber ohne Code kann man da schlecht was zu sagen.
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

@Sirius: Bin grade am Code bearbeiten, deswegen kann ich ihn schlecht zeigen. Meine Idee vom Script:
Ein Ordner (scr = sein Pfad) wird in einen anderen Ordner (dst = sein Pfad) kopiert. Da dst schon exestiert und es normalerweise dann eine Fehlermeldung gibt, wird dst zu dst+'copy' (dst_copy = dst+'copy') gemacht (mit os.rename) und erst danach wird erst scr zu dst kopiert.
Danach kopiert dass Programm alles aus dst_copy in dst kopiert. Falls aber die Datei schon in dst exestier (Falls eine Datei in dst und dst_copy den gleichen Namen hat) , wird standarmäßig die Datei von dst genommen und die Datei von dst_copy gelöscht. Falls ein Ordner schon schon in dst exestiert (Falls ein Order in dst und dst_copy den gleichen Namen hat) geht das Programm in den Ordner rein, und macht genau das gleiche wie davor, nur jetzt im Ordner von dst und dst_copy.
Hast du jetzt vllt etwas mehr verstanden? Wie gesagt, mein Script hilft dir da kaum weiter, ist noch lange nicht fertig.
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Traver: Wie kann man denn bei so maximal 5 bis 7 Variablen den Überblick verlieren? Oder hast Du mehr? Vielleicht sogar ”beliebig” viele in einem intransparenten **kwargs-Wörterbuch, welches nur existiert weil Du nicht immer so viele Argumente bei den Aufrufen hinschreiben wolltest? Dann ist *das* das Problem, und ist es auch bei einer nicht-rekursiven Lösung. Denn ob sich die Laufzeitumgebung Zwischenzustand implizit merkt, oder ob Du da noch zusätzlichen Code schreiben musst, der den Stapel *richtig* verwaltet, macht kaum einen Unterschied. Also ausser den, dass der implizite Aufrufstapel schon das richtige macht — ob Dein selbst geschriebener Stapel-Code das äquivalent macht, oder ob Du dabei auch Fehler oder noch kompliziertere Umwege machst, ist der Unterschied.

Ich würde in jedem Fall erst eine funktionierende rekursive Lösung schreiben und testen. Weil das einfacher ist! Und wenn die nachprüfbar läuft, das ganze iterativ umformulieren. Um Rekursion kommst Du in keinem Fall herum, denn auch iterativ formuliert ist das letztlich ja eine rekursive Lösung, weil es eine rekursive Datenstruktur ist die traversiert wird. Und gerade wenn die Implementierung so komplex für Dich ist, das Du teilweise mit raten/Versuch & Irrtum gearbeitet hast, ist gründliches (automatisiertes) Testen hier sicher wichtiger als alles andere.

Was heisst idiotensicher? Auch eine iterative Lösung hat ihre Grenzen. Klar ist es noch unwahrscheinlicher das Du in einen `MemoryError` läufst als das Du in einen `RecursionError` bei einer rekursiven Lösung läufst, aber auch das halte ich schon für recht unwahrscheinlich. Idiotensicher kann ja auch bedeuten, dass man die Grenzen der Implementierung dokumentiert, statt sie versucht unbegrenzt zu gestalten. Wobei sowohl `os.walk()` als auch `pathlib.Path.rglob()` rekursiv gelöst sind, ohne dass das irgendwo in der Dokumentation steht. Und ich habe bis jetzt auch noch nicht gehört, dass damit jemand an das Rekursionslimit gestossen wäre.

Wirklich idiotensicher kann man nichts machen, denn wenn man etwas idiotensicher macht, erfindet das Universun einfach einen noch grösseren Idioten. 😉 Defensiv programmieren ist an sich eine gute Idee, und man sollte ein wenig auf Fallen achten in die man leicht tappen kann, aber ansonsten sollte man schon von mündigen, denkenden Programmierern ausgehen. Das ist Python und nicht Java. 😜

Wenn Du da gerade neuen Code schreibst, würde ich den an Deiner Stelle nicht mehr mit `os` & Co sondern mit `pathlib` schreiben.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

@_blackjack_: Danke erstmal für die ausführliche Antwort!
Bin jetzt Leider schon wieder relativ weit bei meinem neuen Programm :( . Werder. auch wenn es bei diesem Programm klappt, mal mit pathlib und rekrusionen arbeiten, wie du mir empfohlen hast. Aber ich denke, dass wohl kaum ein MamoryError bei 32, 64 oder 128 Gb (Weiß nicht mehr genau wie viel es waren, am ehesten glaube ich 64 Gb Ram) Ram auftauchen wird (ich programmiere auf dem Server von meinem Vater).
Aber wenn ich eine Funktion nichmal aufrufe, dann werden ja auch die ganzen Iterationen ausgefürht, die ich nicht mehr brauche, oder die sogar Fehler verursachen können. Macht man dann eher einen Parameter der auf True steht, und in seinem eigenen Script auf False stellt, damit er die ganzen unnötigen Funktionen nicht ausführt? Oder schreibt man das Porgramm dann so, dass man diesen Parameter nicht braucht?

(Ich frage sehr viel nach, ich weiß und ich weiß auch dass das nerven kann... Aber ich will etwas ordentlicher programmieren, da ich sehr "unsauber" und "unschön" programmiere)
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Traver: Die Frage(n) verstehe ich nicht. Wenn man Funktionen nicht ausführen will, ruft man sie nicht auf.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Traver
User
Beiträge: 9
Registriert: Dienstag 16. Juni 2020, 13:43

@_blackjack_: Wenn man eine Rekrusion macht, gibt man dann einen Parameter an die Funktion zurück (Die sich graade selbst aufruft),
dass das eine Rekrusion ist, damit die Rekrusion nicht unnötige Iterationen ausführt, die zum Fehler kommen können?

Code: Alles auswählen

def test(Rekrusion = False):
    if Rekrusion == False:
        os.makedirs(/home/[User]/Documents)                                            # irgendein body, ist egal welcher, wird nicht bei rekrusion aufgerufen
        ...
    ...                                                                                               # irgend ein body, ist egal welcher, wird bei rekrusion und bei nichtrekrusion aufgrufen
    test(Rekrusion = True)
test()
Damit z.B nicht jedes mal versucht wird, die Ordner zu erstellen, weil dann gibt es ja einen Fehler...
Oder würde man so ein Problem "schöner" und "effektiver" lösen?
Oder mache ich hier grade ganz großen Müll?
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Irgendwie ist dein Vorgehen an sich seltsam.
Warum iterierst du nicht einfach über alle Dateien in allen Unterverzeichnissen und schaust, ob eben dieser Pfad in dem anderen Verzeischnis auch existiert?
Und ich wie du Hilfe zu Code haben willst, den du partout nicht zeigen willst, leuchtet mir auch nicht ein.
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Traver: Mir ist jetzt nicht so ganz klar was Du mit „nicht rekursiven“ Aufrufen meinst? Wenn etwas nur einmal, vor den ganzen rekursiven Aufrufen passieren soll, dann gehört das nicht in die rekursiv aufgerufene Funktion und schon stellt sich die Frage nicht. Auf das Beispiel bezogen:

Code: Alles auswählen

def test():
    ...
    test()

os.makedirs(/home/[User]/Documents)
...
test()
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten