Reportlab und SaveAs Dialog

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.
Bibo3000
User
Beiträge: 30
Registriert: Mittwoch 5. Oktober 2022, 14:01

Aaaaahhh tausend Dank!
Das es sich dabei um eine globale Variable handelt wusste ich nicht. Das erklärt natürlich alles! Vielen Dank an deets und noisefloor! Somit ist mir jetzt natürlich auch klar, dass ich die Folgen der Änderung vornherein nicht unbedingt absehen kann.

Ich kannte globale Variablen bislang nur durch den Einsatz von "global" und das nutze ich nur dann, wenn ich keine andere Möglichkeit sehe (was nicht heißt, dass es die nicht gibt). Ich habe es beispielsweise genutzt, wenn das Ergebnis einer Funktion an mehreren Ecken gebraucht wird, aber zu unterschiedlichen Zeiten. So kann ich darauf immer wieder zugreifen ohne gleich die Funktion nochmal laufen zu lassen.

Gibt es denn noch weitere systemeigene globale Variablen, wie das Arbeitsverzeichnis? Rein interessenshalber... Wird halt auch nicht alles in Büchern erklärt.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Jede Menge. Das gesamte Environment (os.environ), aber auch jede Menge Python-Objekte. ZB sys.path oder alles moegliche andere. Auch das hier ist eine globale Variablennutzung:

Code: Alles auswählen

dinge = {}

def tuwas():
    dinge["schluessel"] = "wert" # look ma, no global!

tuwas()
Wir hatten hier mal einen Spezi (Alfons M...), der sich standhaft geweigert hat, das als globalen Zustand zu akzeptieren. Ist es aber trotzdem. Nur weil kein global statement vorkommt, ist der Effekt ja trotzdem der gleiche.

Und was deinen bisherigen Einsatz von global angeht: Argument, die eine Funktion braucht, bekommt sie eben als Argument. Also jede andere Funktion in irgendeiner Ecke bekommt halt ein weiteres Argument reingereicht.
Bibo3000
User
Beiträge: 30
Registriert: Mittwoch 5. Oktober 2022, 14:01

Also an der Stelle kann ich stolz behaupten, dass ich das für meinen Teil ebenfalls als globalen Zustand sehe, habe ich bis eben nur nicht effektiv drüber nachgedacht.
Letztendlich habe ich das aber auch schon so genutzt, keine Frage. So gibt es beispielsweise Listen in meinem Programm die entweder von Anfang definiert und unveränderlich sind und an die auf verschiedener Stelle zugegriffen wird, aber auch Listen die genau für den gleichen Zweck erst programmintern nach Berechnungen erstellt werden. Kurz gesagt, sobald global abrufbar = globaler Zustand.

Deinen letzten Satz verstehe ich nicht ganz, schätze aber mal, dass das nicht wieder eine Schelte war. ^^
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

So kurz gesagt ist das nicht. Es ist eine gute Naeherung, aber Python erlaubt ja zB keine echte Konstanten. Da *kann* man immer etwas aendern. Wenn eine solche "Konstante" dann aus einer veraenderlichen Datenstruktur (also zB Liste oder Woerterbuch) besteht, kann man das streng genommen aendern. Aber man *soll* es halt nicht, und dann ist es konzeptionell auch ok. Fuer Listen kann man da im Zweifel stattdessen Tupel nehmen, Sets haben auch frozenset, bei Woerterbuechern kenne ich aus der Lameng aber erstmal keine nicht-veraenderliche Variante. Und mache mir auch nicht die Muehe, nach sowas zu suchen. Python ist eine Konsens-Sprache, keine Bondage-Sprache.

Was den letzten Satz angeht: der bezog sich auf deine Verwendung von globalem Zustand, und wie man den vermeidet. Argumente statt Zustand:

Code: Alles auswählen

def funktion():
    return "wert"
    
def ecke1(argument, wert):
     return argument + wert
     
def ecke2(argument, wert):
     return argument + "X" + wert
     
def main():
    wert = funktion()
    ecke1("hallo", wert)
    ecke2("egal", wert)
Es gibt erstmal also keinen Grund, sich da ein weiteres Argument zu sparen. Wert kann immer reingereicht werden. Wenn das muehselig wird, dann ist ueblicherweise eher OO-programmierung die Antwort. Globaler Zustand laesst sich damit (fast) immer vermeiden.
Bibo3000
User
Beiträge: 30
Registriert: Mittwoch 5. Oktober 2022, 14:01

Erstmal vielen Dank, deine Ausführungen sind sehr interessant und lehrreich.
Ich habe es jetzt zwar nicht als Kritik wahrgenommen, will mich aber mal genauer ausdrücken. Mein Programm ist ein Berechnungsprogramm. Einige Werte oder Listen darin sind, rein für die Berechnung, "Konstanten". Das die erzeugten Objekte keine Konstanten sind ist mir ja klar, das Programm soll sie nur eben so verwenden. Oder anders ausgedrückt, der Benutzer hat an den Werten nix zu suchen und das Programm ließt sie nur aus. Die Listen, Variablen etc. sind selbstverständlich veränderbar, aber nur wenn ich will. ^^

Über deinen Lösungsvorschlag habe ich auch schon einige male gebrütet. Mein Problem, warum ich zu "global" übergegangen bin ist, dass das Ergebnis einer Funktion zu unterschiedlichen Zeitpunkten an unterschiedlichen Ecken gebraucht wird. Ich könnte damit an jeder Ecke die gleiche Funktion aufrufen und das gleiche Argument ziehen, oder die Variable mit dem Ergebnis als global definieren und dann, wenn ich es brauche, auf das Ergebnis der ersten Berechnung einfach zugreifen. Es kommt beispielsweise vor, dass ich eben jene globale Variable in 2 - 3 Funktionen, vielleicht noch einer Klasse und dann im Output und später auf dem PDF brauche. Der Weg über global erschien mir kurz und elegant.

Standartmäßig werden Variablen jedoch außschließlich in der jeweiligen Funktion genutzt. Bei anderen benötigten Werten kann ich die Objekte aus den jeweiligen Klassen ziehen, easy peasy. :)
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bibo3000: Warum kann der Wert nicht einmal berechnet werden und dann an die verschiedenen Funktionen übergeben werden, wie `wert` in dem Beispiel von __deets__? Elegant ist nicht das Wort was mir bei ``global`` als erstes einfallen würde. Das ist eher so wie eine Maschine die aus mehreren Teilen besteht, wo von irgendwoher innerhalb der Maschine zusätzlich angebrachte Leitungen kommen, die etwas aussen ohne Abdeckung angebrachtes, wo jeder dran kommen kann, mit irgendwelchen Stellen in der Maschine verbindet. Neben den vorgesehenen Steckverbindungen zwischen den Teilen. Und wenn man wissen will was das genau für einen Einfluss hat, und was in der Maschine alles Einfluss auf das Teil aussen hat, muss man jeden Teilbereich der Maschine aufmachen und schauen was das da drin genau macht, statt das einen nur interessieren muss was die einzelnen Teile an den Schnittstellen machen, und einen die Innereien nicht interessieren müssen, solange sie sich so verhalten wie das für die Verbindungen vorgesehen ist.

Das ist ja ein Sinn von Funktionen, das man da im Idealfall eine Blackbox hat, in die man was rein steckt und die etwas damit macht und ein Ergebnis liefert. Wissen braucht man nur *was* die Funktion macht, aber nicht *wie* sie das macht. Und man kann jede Funktion einzeln testen, weil die keinen Einfluss auf irgendwas ausserhalb der Argumente haben sollte. Wenn ``global``, oder auch anderweitig globaler Zustand ins Spiel kommt, gilt das nicht mehr, das die Funktion eine in sich abgeschlossene Einheit ist, die man für sich allein betrachten kann. Dann muss man plötzlich auch in alle anderen Funktionen rein schauen ob die durch eine Änderung des globalen Zustands betroffen sind und wie, und auch welche Funktionen denn den Zustand auch ändern können. Und es reicht auch nicht nur in diese Funktionen direkt rein zu schauen, denn es ist ja auch wichtig *wann* die von *wo* aufgerufen werden, und schon muss man potentiell erst einmal alle Funktionen im Auge haben und welche Funktion welche aufruft und unter welchen Umständen.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Mein Problem, warum ich zu "global" übergegangen bin ist, dass das Ergebnis einer Funktion zu unterschiedlichen Zeitpunkten an unterschiedlichen Ecken gebraucht wird. Ich könnte damit an jeder Ecke die gleiche Funktion aufrufen und das gleiche Argument ziehen, oder die Variable mit dem Ergebnis als global definieren und dann, wenn ich es brauche, auf das Ergebnis der ersten Berechnung einfach zugreifen.
Das ist doch genau der Fall, wo man `global` _nicht_ benutzen will, weil sich das Ergebnis grundsätzlich ändern könnte. Dein Codebeschreibung verlässt sich aber darauf, dass genau das nicht der Fall ist. Außerdem macht es deinen Code schwerer nachvollziehbar (und damit schlechter wartbar), weil sich ja scheinbar darauf verlassen wird, dass die Variable $FOO global definiert ist und nicht explizit übergeben wird.

Oder kurz gesagt: das ist nach deiner Beschreibung einer der 99% Fälle, wo die Verwendung von `global` ziemlich sicher falsch ist.

Gruß, noisefloor
Bibo3000
User
Beiträge: 30
Registriert: Mittwoch 5. Oktober 2022, 14:01

Ich verstehe all eure Argumente gut und vielen Dank für die ausführlichen Erklärungen. :)
Das Programm führt nicht nur eine Rechnung durch, sondern unterschiedliche zu unterschiedlichen Zeitpunkten und genau das ist m.E. der Punkt. Die Zeit. Das Ergebnis von Berechnung A brauche ich danach in B und später in F. Es ist aber nicht eine Funktion die durchläuft oder eine Klasse, sondern ein Mix aus allem und die Ergebnisse werden untereinander gebraucht. Im Fall von Klassen (hier könnte ich die Funktionen natürlich auch in Klassen ändern und auf global verzichten), kann ich die Ergebnisse aus der Klasse ziehen. Bei Funktionen ist mir nicht bekannt, dass das Ergebnis innerhalb der Funktion gespeichert wird. Mir ist das erst mit global gelungen, einer Variable einen Wert zu geben und diesen dann, zu unterschiedlichen Zeiten in unterschiedlichen Funktionen und Klassen zu nutzen.

Es ist eben etwas kompliziert und ohne explizites Beispiel wahrscheinlich beschi**en zu verstehen, aber mein Code ist inzwischen 1300 Zeilen lang und umfasst viele unterschiedliche Funktionen und Klassen. Aber ein kleines Beispiel.
Der Anwender gibt zwei unterschiedliche Gruppen von Werten ein. Ich habe eine Klasse erstellt, die aus jeder Gruppe den Mittelwert berechnet. Auf jeden der beiden Mittelwerte kann ich jetzt zugreifen. Es folgen weitere Berechnungen durch Klassen. Am Ende steht eine Funktion, die ein Endresultat mit den Ergebnissen der Klassen errechnet. Dabei handelt es sich um ein "zentrales" Ergebnis und quasi anhängende "Unterergebnisse" in Form einer Liste und die speichere ich in einer globalen Variable bzw. Liste ab und kann so über weitere Funktionen die am Ende z.B. eine Übersicht und die PDF liefern auf die Ergebnisse zugreifen. Vorbenanntes "zentrales" Ergebnis wird aber ebenso in anderen kleineren Funktionen weiterverwendet und führt wiederum zu anderen Ergebnissen, die ebenfalls in die Übersicht kommen.

Letztendlich denke ich, hinkt der Vergleich mit dem Motor ein bisschen, denn, bei mathematischen Berechnungen bzw. Formeln ist klar, welcher Wert benötigt wird um zum gewünschten Ergebnis zu gelangen und wenn dieser Wert auch noch "Mittelwert" heißt, ist auch klar, woher der kommt. Testen lässt sich trotzdem jede Funktion für sich, zumindest habe ich das immer gemacht. Diese globalen Variablen haben auch absolut eindeutige Namen und kommen im ganzen Code genau einmal vor. Bislang lief das komplett problemlos.
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

@Bibo3000: alle Deine Argumente, die Du hier bringst, sind ja genau die Fälle, wo Du mit global Probleme bekommst: Langes Programm, viele unterschiedliche Funktionen, etwas wird berechnet und erst später verwendet...
Und keines Deiner Argumente widerspricht __deets__, der einfach sagt, wenn irgendwo ein Wert gebraucht wird, muß man diesen Wert per Argument übergeben.

Weitere Punkte, die sich seltsam anhören: einen Mittelwert zu berechnen, ist Aufgabe einer Funktion, und nicht einer Klasse.
Wenn Du ein Ergebnis hast (das dann idealerweise in Form einer Klasse vorliegt, weil es ja scheinbar eine Komplexere Struktur hat), dann ist das ja genau das Objekt, das man dann einfach überall herumreichen kann, um es dann als PDF auszugeben.

Die Lösung ist also ziemlich trivial, kann aber schwierig werden, umzusetzen, wenn Du schon viel Code geschrieben hast, der keine klare Struktur aufweist. Trotzdem lohnt sich der Aufwand, globalen Zustand loszuwerden, vor allem bevor aus den 1000 Zeilen 2000 werden.
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bibo3000: Doch, es ist eine Funktion die durchläuft. Die Hauptfunktion. Und die ruft andere Funktionen (und Methoden) direkt oder indirekt auf, die auch durchlaufen, und die alle Argumente entgegen nehmen wo man die Werte die gebraucht werden, übergeben kann. Um Dein Beispiel zu nehmen:

Code: Alles auswählen

def main():
    some_result = calculation_a()
    calculation_b(some_result)
    calculation_c()
    calculation_d()
    calculation_e()
    calculation_f(some_result)
Das ändert sich auch nicht wenn das in einer anderen Funktion als `main()` seinen Ursprung hat und wenn da sehr wahrscheinlich noch mehr Argumente und Ergebnisse bei den einzelnen Funktionen beteiligt sind. Die Aufrufe spannen einen Baum auf wo über definierte Schnittstellen über Argumente und Rückgabewerte die Daten fliessen. Und mit globalen Variablen schaffst Du zusätzliche, durch die Schnittstellen nicht sichtbare Datenverbindungen zwischen beliebigen Knoten im Baum, die man erst nachvollziehen kann, wenn man sich den kompletten Baum vorstellt und in jeden Knoten rein schaut was der konkret macht. Statt das man sich einen beliebigen Knoten raussuchen kann, und sich einfach dessen Eingänge anschauen kann, und was der ausgibt, ohne das wissen muss von wo der Zulauf der Daten kommt, oder wie der Unterbaum aussieht. Weil man weiss, dass das was man da rein steckt das Ergebnis berechnet, und weder von irgendwelchen Aufrufen abhängt die man zusätzlich noch vorher machen muss, von denen man wissen muss, noch das irgendwelcher Zustand verändert wird, der andere Aufrufe durch undurchsichtigen globalen Zustand beeinflusst.

Wie testest Du eine Funktion die globalen Zustand braucht und verändert? Die kannst Du ja nicht einfach aufrufen ohne vorher den globalen Zustand zu setzen und hinterher eventuell auch noch prüfen ob der korrekt ist. Damit ist die nicht für sich einzeln testbar. Und das betrifft dann *jede* Funktion, auch die die den globalen Zustand weder verändern noch benutzen, denn das muss man ja erst einmal wissen das sie das a) nicht selber tun, und b) das sie auch keine Funktion aufrufen die das tut, weder direkt, noch indirekt, und schon muss man das gesamte Programm kennen um einzelne Funktionen testen zu können. Das ist nicht das was mit einzeln testbar gemeint ist.

Du sagst Du weisst wo der globale Mittelwert überall benutzt wird — der Punkt ist das solltest Du gar nicht wissen müssen. Und das weisst Du nur weil Du das Programm gerade schreibst. Man ist nach ein paar Monaten oder einem Jahr irgendwann bei eigenem Code ein fremder Leser, der sich da erst wieder einarbeiten muss. Und das meistens weil man einen Fehler sucht, oder das Programm erweitern will/muss. Und je länger das Programm ist, und je mehr globaler Zustand da drin steckt, um so schwieriger wird es sich da einzuarbeiten, Fehler zu finden, Änderungen und Erweiterungen vorzunehmen und sich dabei sicher sein, dass das nicht irgendwo an anderer Stelle noch weitere Änderungen nach sich ziehen müsste, die man aus den Schnittstellen der Funktionen und Methoden nicht ersehen kann.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Bibo3000
User
Beiträge: 30
Registriert: Mittwoch 5. Oktober 2022, 14:01

Danke euch allen!
Ich werde/muss meinen Code (aus anderen Gründen) ohnehin nochmal durchgehen. Mein Programm ist auch noch nicht fertig. Ich werde mir eure Ratschläge zu Herzen nehmen und mal sehen wie und wo ich es optimiert bekomme und versuchen die Struktur noch klarer zu machen, besonders da ich noch deutlich mehr an Funktionen geplant habe. Es ist ja noch kein Profi vom Himmel gefallen und das ist tatsächlich mein erster Versuch selbst etwas zu programmieren. Den Code nochmal zu verbessern übt und trainiert auch. Danke für den konstruktiven und hilfreichen Input. :)

@ Sirius3: Das mit dem Mittelwert war so eine Sache. Ich habe zwei Datensätze für die jeweils ein Mittelwert ermittelt werden soll. Das über die Klasse zu lösen erschien mir einfach.
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bibo3000: Die Mittelwertbestimmung selbst ist doch eine Funktion. Die man für jeden der beiden Datensätze aufrufen kann. Und die es ja auch bereits gibt: `statistics.mean()`. Ich sehe nicht wo an der Stelle eine Klasse ins Spiel kommen würde. Eventuell wäre eine Datenklasse sinnvoll um die beiden Werte oder auch noch Datensätze zu einem Objekt zusammen zu fassen. Damit man nicht 2 bis 4 Einzelwerte als Argumente/Ergebnis herumreichen muss, die gemeinsam eine Bedeutung haben für die man einen sinnvollen Namen finden kann.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Bibo3000
User
Beiträge: 30
Registriert: Mittwoch 5. Oktober 2022, 14:01

Wie gesagt, erstes Programm, arbeite mit Python effektiv seit August, hab nachsicht. ;-)

Edit: Wobei effektiv, zwischen Job, Familie und allen anderen Verpflichtungen des täglichen Lebens heißt. ^^
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

IMHO machst du einen unter Einsteigern und fortgeschrittenen Einsteigern populären Fehler, sich `global` schön zu reden, weil es in manchen Fällen so schön einfach ist. Was aber damit einhergeht, dass es in den meisten Fällen falsch ist, siehe oben.

Klar kannst du es verwenden und natürlich kann es funktionieren. Was du in deinem Programmiererkämmerlein treibst ist deine Sache. Aber wenn du so Code hier im Forum zeigst und Fragen dazu hast bekommst du es halt immer und immer wieder um die Ohren gehauen. Mit Recht.

Ich bin auch "nur" Hobbyprogrammierer. Darum kann man trotzdem versuchen, den bestmögliche Code zu schreiben und offensichtlich vermeidbare Fehler zu vermeiden. Dazu gehört eben auch, kein `global` zu nutzen, sondern den Code so gut zu strukturieren, dass es sowieso gar nicht nötig ist.

Gruß, noisefloor
Antworten