OOP: Grundsätzlich instanzieren? Nötig oder Stilfrage?

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.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

sparrow hat geschrieben: Mittwoch 4. Mai 2022, 18:05 @LukeNukem: Niemand rät davon ab, irgendwelche Features zu verwenden.
Das nehme ich anders wahr, zum Beispiel an der ersten Antwort in diesem Thread. Wenn ein Anfänger so eine felsenfeste Aussage wie "Klassen sind kein Selbstzweck" liest, dann denkt er sich "oh, dann benutze ich lieber keine Klassen, wenn ich das irgendwie vermeiden kann", und am Ende kommt dann das heraus, was der TO hier fragt.
sparrow hat geschrieben: Mittwoch 4. Mai 2022, 18:05 Aber eine Klasse zu verwenden, nur weil man gerne eine Klasse verwenden möchte ist falsch.
"Falsch"? Ist das nicht ein kleines bisschen anmaßend? Seit wann bist Du der Richter über richtig und falsch, und wer hat Dich dazu gemacht, wer beauftragt?
sparrow hat geschrieben: Mittwoch 4. Mai 2022, 18:05 Hier - und das wurde ausdrücklich gesagt - ist es natürlich mehr als sinnvoll eine Klasse zu verwenden.
...und trotzdem beschäftigt sich ein erheblicher Teil der Beiträge damit, wie man ebendies vermeiden könnte.
sparrow hat geschrieben: Mittwoch 4. Mai 2022, 18:05 Die javagroteske Art, wie du hier in der Vergangenheit versucht hast, _alles_ in Klassen zu stopfen ist halt in Python unüblich. Man braucht es halt nicht.
"Javagroteske Art"... ei der Daus, geht's noch? Dabei habe ich schon mehrere Male ausgeführt, daß ich Java nicht mag und auch in keiner Weise davon beeinflußt bin...

Ja, man kann eine Menge ohne Klassen machen, das konnten wir früher in den alten Nicht-OOP-Sprachen ja auch schon. Aber warum sollte ich den Einsatz eines der wichtigsten Kernfeatures meiner Sprache vermeiden? Gibt es dafür irgendwelche sachlichen und fachlichen Gründe, und wenn ja: welche?
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@LukeNukem: das hatten wir doch schon oft durchgekaut: wenn eine Klasse die Komplexität des Programms erhöht, statt sie zu verringern, dann ist eine Klasse das falsche Mittel.
Python ist so eine Mächtige Sprache, dass man vieles ohne Klassendefinitionen lösen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Und der Ausgangsbeitrag ist ja ein gutes Beispiel dafür, dass die Klasse(n) dort problemlos zu einer 5 Zeilen langen Funktion zusammenschrumpfen und die Klasse dort keinen Mehrwert bringt.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Donnerstag 5. Mai 2022, 08:02 Und der Ausgangsbeitrag ist ja ein gutes Beispiel dafür, dass die Klasse(n) dort problemlos zu einer 5 Zeilen langen Funktion zusammenschrumpfen und die Klasse dort keinen Mehrwert bringt.
Muß eine Klasse denn unbedingt einen Mehrwert bringen? Ist es nicht vollkommen ausreichend, wenn sie keinen Wenigerwert bringt?

Python ist eine objektorientierte Programmiersprache. Ich sehe immer noch nicht, daß es ein Fehler wäre, wenn jemand Features benutzt, die fest zum Kern des Sprachumfangs gehören, die in dieser und anderen Sprachen absolut üblich und gebräuchlich, und die auch Entwicklern aus anderen Sprachen flüssig geläufig sind. Wer seine Implementierung schon einmal jemandem erklärt hat, der in einer anderen Sprache zuhause ist, weiß diesen Vorzug sicherlich sehr zu schätzen. Nicht nur, aber auch vor diesem Hintergrund sehe ich zudem nicht, daß functools.partial, Closures oder Lambdas wirklich einfacher oder in irgendeiner Weise "wertvoller" wären als als die schlichte und einfache Verwendung von Pythons OO-Features.

Mein wesentlicher Punkt ist jedoch, daß eine Klasse in dem geschilderten Fall zunächst keinerlei Wenigerwert bringt -- und nein, sorry, aber einige wenige Zeilen Code sind in meinen Augen noch kein echter Wertverlust. Zudem ergibt sich hier aber sofort ein echter Mehrwert, sobald Zustände ins Spiel kommen -- und genau das wurde dann ja in der Folge auch ein Gegenstand dieses Threads. An dieser Stelle hast auch Du Dich dann ja, wenn ich Dich richtig verstanden habe, sehr deutlich für die Verwendung von Klassen ausgesprochen.

Nun habe ich in den letzten Jahren eine ganze Reihe von GUI-Pogrammen geschrieben und kann mich nicht daran erinnern, daß eines dabei gewesen wäre, das keinerlei Zustände genutzt hätte. Wenn ich Euch dabei gefolgt wäre und bei meinem ersten Ansatz eine prozedurale Lösung gewählt hätte, hätte ich diese komplett umbauen müssen, sobald Zustände ins Spiel gekommen wären. Zugegeben: das ist im hier diskutierten Minimalbeispiel zwar noch ein relativ überschaubarer Aufwand und daher kein großer Verlust. Dennoch ist es ein Wenigerwert des prozeduralen Ansatzes, in einem so vorhersehbaren Fall ein Refactoring zu erfordern. (Dies gilt umso mehr, wenn man die Widerstände gegen ein Refactoring aus einer Praxis kennt, in der dann häufig leider doch einfach eine globale Variable eingeführt würde).

Ja, ich kenne das YAGNI-Prinzip und folge ihm, halte es aber trotzdem für einen enormen Mehrwert, zukunftsfähig zu entwickeln und zumindest für besonders offensichtliche und leicht vorhersehbare Fälle (wie hier eben der Einführung eines Zustandes) vorbereitet zu sein. Da hat die objektorientierte Programmierung enorme Vorteile, insbesondere hinsichtlich der Strukturierung, der Wart- und der Wiederverwendbarkeit von Code.

Und dann ist es mein Eindruck, daß Fragen wie die des TO auch deswegen gestellt werden, weil hier so häufig "dafür braucht man keine Klassen" gepredigt wird. Auch die anderen Antworten auf meine Beiträge ("javagrotesk", "Komplexität") scheinen mir primär dazu geeignet zu sein, Anfänger abzuschrecken: die eine Antwort suggeriert, die Nutzung von OO-Features sei "grotesk" und die andere, OOP sei "komplex" -- oder sogar zu komplex -- und sollte deswegen wo möglich vermieden werden.

Wie dem auch sei: in diesem Thread hat ein Einsteiger eine Frage nach der elegantesten Anwendung von Objektorientierung gestellt. Wohlgemerkt: er hat nicht gefragt, wie er OOP vermeiden kann. Vor diesem Hintergrund erscheint es mir dann doch etwas merkwürdig, ihm zu sagen, daß er für sein Beispiel (sic!) eigentlich gar keine OO braucht. Selbst wenn das für sein ursprüngliches Beispiel zutrifft, ist das keine Antwort auf seine Frage, und Aussagen, die "vermeide Objektorientierung, wo Du kannst" suggerieren, haben IMHO verheerende pädagogische Effekte. Da sollte man vielleicht auch mal über die Zielgruppe nachdenken, scheint mir.

Dies gilt vor allem auch vor dem Hintergrund, daß hier ja üblicherweise keine Applikationen aus der realen Welt, sondern nur kurze Beispiele zur Illustration gezeigt werden, bei denen es naturgemäß deutlich einfacher ist, auf Klassen zu verzichten. Man kann ja unter Fortgeschrittenen und Profis immer gerne darüber diskutieren, ob und gegebenenfalls wie man OO-Features vermeiden kann, aber den Einsteigern hier sendet das leider häufig die falschen Signale. Insofern, nur daß wir uns richtig verstehen: man kann (kann, nicht muß!) einem Anfänger zwar sagen, daß vieles auch ohne OO geht, aber er sollte dabei nicht entmutigt werden, OO zu benutzen.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich weiß nicht so genau was du hier gelesen hast, aber der TE hat genau NICHT nach OO gefragt, sondern danach, ob eine Instanz, von der er schon wusste, wie er die nutzt, nicht auch durch eine Klasse ohne Instantiierung ersetzt werden kann. Wovon ihm abgeraten wurde. Hier wurde also jemand genau im Gegenteil ermutigt zur OO. Unter der Voraussetzung, das es sinnvoll ist, weil eben Zustand existiert.

Und auch sonst ist deine Wahrnehmung hier verzerrt. In fast jedem posting zum GUI Thema, bei dem die Leute hier mit globalem Zustand gespicktem Code einreiten, wird erwähnt, dass man ohne OO eigentlich keine GUI Entwicklung betreiben kann. Zb hier viewtopic.php?t=54167, und das ist nur eines von vielen Beispielen.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__deets__ hat geschrieben: Donnerstag 5. Mai 2022, 19:41 Ich weiß nicht so genau was du hier gelesen hast,
Ja, das weiß ich auch nicht. Es scheint, als zeige das Forum die Beiträge selektiv je nach Leser, so daß ich hier Dinge lese, die andere nicht zu sehen bekommen. ;-)

Nein, im Ernst: der TO zeigt -- wie das hier ja auch oft gefordert wird -- zwei minimale Codebeispiele, um seine Frage zu verdeutlichen. Okay, die Beispiele sind ein bisschen quatschig und die Frage eigentlich auch, aber er ist halt ein Einsteiger und (noch?) kein Profi.

Die erste Antwort: "Klassen sind kein Selbstzweck". Einerseits richtig, andererseits geht diese Antwort an der Frage vorbei, und einem Einsteiger signalisiert sie: "Klassen sind böse und Du darfst sie nur dann benutzen, wenn es gar nicht anders geht".

Darauf folgt die zweite Antwort mit einem Verweis auf einen Thread, in dem Du schreibst: "Ich halte wenig von Vererbung aus Prinzip" und dann sagst, das sei "ueberfluessig", da ich die Klasse Tk nicht überlade (ich möchte die Diskussion nicht wiederbeleben, aber ich überlade zwar keine Methode, aber ich überschreibe eine, und auch das ist OOP). Für einen Einsteiger liest sich das wie: "Vererbung ist doof und darf nur benutzt werden, wenn eine Überladung stattfindet".

Die dritte Antwort spricht von "code smell" und empfiehlt: "'Das Programm würde man einfach als Funktion schreiben". Also ich persönlich weiß ja nicht, wer dieser "man" ist, aber ich würde das definitiv nicht tun, sondern den in anderen Sprachen und Frameworks etablierten Software Design Patterns folgen und sogar das mit einer Klasse implementieren, wie in dem Thread, der in Antwort zwei verlinkt wurde. Immerhin antwortet __blackjack__ in seiner Antwort erstmals tatsächlich auf die Frage des TO und weist dabei sogar endlich darauf hin, daß die Sache wieder ganz anders aussieht, sobald Zustände ins Spiel kommen.
__deets__ hat geschrieben: Donnerstag 5. Mai 2022, 19:41 aber der TE hat genau NICHT nach OO gefragt, sondern danach, ob eine Instanz, von der er schon wusste, wie er die nutzt, nicht auch durch eine Klasse ohne Instantiierung ersetzt werden kann. Wovon ihm abgeraten wurde. Hier wurde also jemand genau im Gegenteil ermutigt zur OO. Unter der Voraussetzung, das es sinnvoll ist, weil eben Zustand existiert.
Hmmm... die Frage, ob eine Klasse instanziiert oder als Klassenobjekt genutzt werden sollte, ist IMHO durchaus eine Frage nach der Anwendung von Objektorientierung. Was denn sonst? Und daß die aller-, aller-, allerwenigsten GUI-Programme ohne Zustände auskommen, darauf können wir uns doch einigen, oder?
__deets__ hat geschrieben: Donnerstag 5. Mai 2022, 19:41 Und auch sonst ist deine Wahrnehmung hier verzerrt.
Ach, das mit den verzerrten Wahrnehmungen ist ja so eine Sache... Es ist allerdings offensichtlich zutreffend, daß meine Wahrnehmung manchmal eine andere ist als die der hiesigen meinungs- und wortstarken Mehrheit. Damit kann ich aber ganz gut leben und hoffe, Ihr auch. ;-)
__deets__ hat geschrieben: Donnerstag 5. Mai 2022, 19:41 In fast jedem posting zum GUI Thema, bei dem die Leute hier mit globalem Zustand gespicktem Code einreiten, wird erwähnt, dass man ohne OO eigentlich keine GUI Entwicklung betreiben kann. Zb hier viewtopic.php?t=54167, und das ist nur eines von vielen Beispielen.
Wenn wir nun aber (im Übrigen sehr richtig) feststellen, daß "man ohne OO eigentlich keine GUI Entwicklung betreiben kann", dann ist die logische Konsequenz, daß Antworten mit dem Tenor "also dafür braucht man keine Klassen", "das geht doch mit einer Funktion" und so weiter zwar im Kern vielleicht nicht unbedingt falsch, hier aber weder zielführend noch sinnvoll sind. Funktionale GUI-Entwicklung ist sicherlich eine nette akademische Fingerübung, aber praktisch irrelevant, und ein größeres prozedurales GUI-Programm ist nach meinen Erfahrungen nahezu unlesbar, unwartbar, und so gut wie gar nicht wiederverwendbar.

Insofern wäre es IMHO eine viel bessere Antwort für den TO gewesen, zum Beispiel zu sagen: "Was Du da in Deinem Beispiel machst, ist ja alles andere als der Regelfall und könnte auch funktional implementiert werden. In der Regel wirst Du aber Zustände brauchen, die von der Interaktion des Users mit den Bedienelementen verändert werden, und dann ist eine Instanz einer Klasse der richtige Ansatz."
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Die erste Antwort: "Klassen sind kein Selbstzweck". Einerseits richtig, andererseits geht diese Antwort an der Frage vorbei, und einem Einsteiger signalisiert sie: "Klassen sind böse und Du darfst sie nur dann benutzen, wenn es gar nicht anders geht".
Woher nimmst du die Gewissheit, dass aus dieser Aussage die von dir unterstellte Interpretation folgt?
LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Für einen Einsteiger liest sich das wie: "Vererbung ist doof und darf nur benutzt werden, wenn eine Überladung stattfindet".
Auch hier wieder: woher nimmst du die Gewissheit, dass diese Interpretation die Konsequenz ist? Wieso wird aus "hier nicht sinnvoll meiner Meinung nach" ploetzlich "niemals sinnvoll"?

Du gehst sogar so weit zu sagen
LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Selbst wenn das für sein ursprüngliches Beispiel zutrifft, ist das keine Antwort auf seine Frage, und Aussagen, die "vermeide Objektorientierung, wo Du kannst" suggerieren, haben IMHO verheerende pädagogische Effekte. Da sollte man vielleicht auch mal über die Zielgruppe nachdenken, scheint mir.
Nicht nur sind die gegebenen Antworten nicht in deinem Sinne, weil sie Objektorientierung anders bewerten, nein, sie sind sogar schaedlich fuer den Fragesteller!

Da stellt sich dann mir gleich die Frage, die du selbst aufgeworfen hast:
LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Seit wann bist Du der Richter über richtig und falsch, und wer hat Dich dazu gemacht, wer beauftragt?
Und warum ist die laut deinem eigenen Eingestaendnis unnuetze Verwendung von OO, siehe
LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Ja, ich kenne das YAGNI-Prinzip und folge ihm, halte es aber trotzdem für einen enormen Mehrwert, zukunftsfähig zu entwickeln und zumindest für besonders offensichtliche und leicht vorhersehbare Fälle (wie hier eben der Einführung eines Zustandes) vorbereitet zu sein.
keine unerwuenschten Nebenwirkungen, wie du sie deinerseits unseren Antworten unterstellst? Kannst du in die Zukunft sehen, und garantieren, dass aus den Fragestellern keine YAGNI-produzierenden Over-Engineers werden?

LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Wenn wir nun aber (im Übrigen sehr richtig) feststellen, daß "man ohne OO eigentlich keine GUI Entwicklung betreiben kann", dann ist die logische Konsequenz, daß Antworten mit dem Tenor "also dafür braucht man keine Klassen", "das geht doch mit einer Funktion" und so weiter zwar im Kern vielleicht nicht unbedingt falsch, hier aber weder zielführend noch sinnvoll sind.
Das ist keine logische Konsequenz. Das ist dein persoenlicher Wunsch oder Wahrnehmung, und du kannst diese Philosophie hier auch gerne selbst zur Anwendung bringen. Was du aber gerade probierst (und anderen dabei ankreidest!), ist deine Meinung als *die richtige* darzustellen. Du arbeitest hier mit einer Menge an Unterstellungen, welche dramatisch negativen Konsequenzen das haben wuerde, wenn man die Leute nicht so frueh wie moeglich an OO gewoehnt. Und das voellig unbelegt.
LukeNukem hat geschrieben: Freitag 6. Mai 2022, 08:59 Insofern wäre es IMHO eine viel bessere Antwort für den TO gewesen, zum Beispiel zu sagen: "Was Du da in Deinem Beispiel machst, ist ja alles andere als der Regelfall und könnte auch funktional implementiert werden. In der Regel wirst Du aber Zustände brauchen, die von der Interaktion des Users mit den Bedienelementen verändert werden, und dann ist eine Instanz einer Klasse der richtige Ansatz."
Das kannst du ja dann so sagen. Als lautstarke Mindermeinung.
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Ich bin so frei, mich hier als TO noch einmal einzumischen. Keiner von euren Beiträgen hat mich verwirrt oder in eine Richtung gedrängt. Ja, ich habe das Programmieren mit BASIC erlernt und war die letzten Jahre ausschließlich in Dialekten unterwegs (BlitzBasic, PureBasic u. a.), aber ich habe den Weg zu Python gefunden und natürlich möchte ich OOP nutzen, das versteht sich von selbst. Ich denke, dass man viele Probleme durch funktionale Programmierung lösen kann (auch wenn es manchmal umständlich sein mag), aber die OOP hat durch die Möglichkeit der sauberen Strukturierung und vor allem Vererbung unglaublich charmante Möglichkeiten, das ist für mich schnell deutlich geworden.
Ja - es waren "quatschige", kurze Beispielcodes - das Projekt, an dem ich bastele, ist etwa 700 Zeilen lang und natürlich habe ich versucht, das "Kernproblem" herauszunehmen (was einem Anfänger vielleicht nicht einmal gelingt). Ich möchte OOP "atmen", ich möchte es verstehen. Bisher habe ich funktional entwickelt, so denke ich (noch). Also bin ich auf der Suche nach einer Stilistik gewesen, die meinem eigenen, persönlichen Stil entspricht. Ich habe mein Programm funktional entwickelt und zusätzlich OOP verwendet, weil ich Objekte (Plural!) brauchte, die voneinander erben sollten, weil sie bestimmte Funktionalität teilten, an anderen Stellen differieren. Ich habe mit tkinter programmiert - und absolut richtig, wie ihr schon antizipiert habt: Ich hatte zwei Funktionen, wo ich auf globale Variablen zurückgegriffen habe. Das war bei meinen Basic-Programmen Standard (und ehrlich gesagt, hatte ich noch nie ein Problem mit einer "fälschlichen" Überschreibung oder Problemen bei der Wartung - allerdings habe ich nicht im Team gearbeitet und kein Programm war länger als etwa 10000 Zeilen). Aber das ist nicht der pythonic way of life - das ist mir klar.
Also habe ich angefangen, mein funktional konzipiertes Programm in eine Klasse umzudekorieren (und ich meine keinen decorator, sondern verwende das Wort, weil mir klar ist, dass es so nicht Fisch und Fleisch ist). Ich habe fleißig mit Klassenvariablen gearbeitet und sehe hier durchaus schon einen Unterschied zu der rein funktionalen Lösung, obwohl es letztlich eine Spiegelung des Programms ist. Das Programm ist in sich gekapselt, dass "von außen" zugegriffen werden kann, ist gewollt. Das Programm lief, wie erwartet. Und ich empfand es auch nicht als "Schande", denn ein einziges Objekt zu erzeugen, nur damit es "objektorientiert" ist, fand ich nicht naheliegend oder nötig. Wozu app=App(), wenn doch sowieso nur der Hauptcode ausgeführt wird und ich gar keine Instanz benötige? Wiederverwertbar soll dieser Hauptcode auch nicht sein.
Nun bin ich aber keineswegs "beratungsresistent", wie es jetzt scheinen mag - im Gegenteil: Ich will lernen. Ich schätze eure Kompetenz und finde es beachtlich, dass ihr eure Zeit und euer persönliches Engagement in solch ein Forum steckt, ohne monetär entlohnt zu werden. Wenn ich mir ansehe, wie viele Beiträge ihr geschrieben habt - Hut ab!

Also: Ich habe den Code nun instanziert, bin aber auf das Problem gestoßen, dass ich die Klassenvariablen schmerzlich vermisse, weil ich es nicht hinbekomme, die einzelnen Objekte miteinander vernünftig kommunizieren zu lassen. Wenn ich command=funktion von der Button-Klasse von tkinter nutze, habe ich ja keine Rückgabewerte. Ich muss auf Attribute innerhalb der Klasse zugreifen. Kein Problem. Was aber, wenn ich auf Attribute einer anderen Klasse zurückgreifen möchte? Mit der Klassenvariablen-Methode war das ein Kinderspiel, aber bei einer strengen Objektbehandlung stehe ich (noch?) auf dem Schlauch.

Die Herausforderung in meinem Programm ist, dass die eine Klasse (nennen wir sie einfach App), die das eigentliche Programm darstellt, auf andere Klassen zurückgreift, die ihrereseits Widgets erstellen und ihre eigenen Methoden mitbringen. Sie müssen aber auf Attribute der Klasse App (nein - korrigiere - nun auf das Objekt der Klasse App :roll: ) Zugriff nehmen.

Wieder zwei quatschige Beispiele, um mein Problem zu schildern bzw. ein Ansatz, wie es vielleicht zu lösen wäre:
Klassen-Variablen (der schlechte Weg, das habe ich verstanden und will ich ja besser machen, aber hier noch einmal zur Verdeutlichung):

Code: Alles auswählen

import tkinter as tk

class App:
    summe = 0
    root = tk.Tk()
    objekte = []

    def lets_go():
        tk.Button(master=App.root, text="Objekt erzeugen", command=App.make_object).pack()

    def make_object():
        App.objekte.append(AnotherClass())
        print("Objekte in der Liste:", len(App.objekte))
    
class AnotherClass:
    def __init__(self):
        tk.Button(master=App.root, text="summe +1", command=self.addiere).pack()

    def addiere(self):
        App.summe += 1
        print("Summe:", App.summe)


def main():
    App.lets_go()
    App.root.mainloop()

if __name__ == "__main__":
    main()
    
Ich habe verstanden, dass euch bei so einer Version die Haare zu Berge stehen, obwohl ich den Mehrwehrt gegenüber der rein funktionalen Lösung mit dem Befehl "global" durchaus schon sehe - und das Programm arbeitet und macht Spaß. Aber ich will es ja besser machen.
Mein neuer Ansatz (der aber vielleicht auch schon der falsche ist?):

Code: Alles auswählen

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.summe = 0
        self.objekte = []
        tk.Button(master=self, text="Objekt erzeugen", command=self.make_object).pack()

    def make_object(self):
        self.objekte.append(AnotherClass(self))
        print("Objekte in der Liste:", len(self.objekte))

class AnotherClass:
    def __init__(self, main_class):
        self.main_class = main_class
        tk.Button(master=self.main_class, text="summe +1", command=self.addiere).pack()

    def addiere(self):
        self.main_class.summe += 1 # So macht man das dann? 
        print("Summe:", self.main_class.summe)


def main():
    app = App()
    app.mainloop()

if __name__ == "__main__":
    main()
    
Ist es so "richtig" gelöst? Muss ich also bei der Erzeugung der Objekte aus meinem App-Objekt das App-Objekt selbst (self) an die AnotherClass() übergeben? Für mich ist das noch recht beschwerlich und wirkt tatsächlich "kompliziert" im Gegensatz des funktionalen Ansatzes, aber ja - ich gebe mein Bestes.
Und ich muss doch darauf hinweisen, dass die meisten Tutorials über tkinter da draußen NICHT objektorientiert aufgebaut sind. Die besten Ansätze habe ich hier gefunden: https://www.pythontutorial.net/tkinter/ ... ed-window/
Aber was sonst so unterwegs ist, ist veraltet oder für einen NOOB kaum lesbar ...

Ich hoffe, wir können den Fachdiskurs unter euch (nenne ich es einmal so) aufgeben und zurück auf meine "ganz kleinen" Probleme kommen. Die Grundsatzdiskussion ist IMHO nicht notwendig. Ich will ja folgen und brav sein! Und ich freue mich über alle Beiträge gleichermaßen - in welche Richtung sie auch gehen. Jeder Mensch ist anders, jeder denkt anders, jeder hat andere Wege, die Probleme anzugehen. Und Python ist so flexibel, dass es die unterschiedlichsten Werkzeuge anbietet, um den Weg zu gehen, wie auch immer man ihn gestalten möchte. Python wird damit vielen Denkweisen gerecht. Das ist doch klasse! Das ist kein Bug. :lol:

Herzliche Grüße
Onomatopoesie
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe es schonmal anklingen lassen, aber nochmal ganz explizit: globaler Zustand ist durch seine Semantik definiert, sprich: er ist von ueberall aus erreichbar, und Aenderungen daran wirken sich dann auch auf andere Codeteile aus, ohne, dass man den Zusammenhang immer gleich erkennt. Ob da das Schluesselwort global (im Fall von Python) vorkommt, ist da unerheblich.

Nun zu deinem konkreten Problem: das hast du dir genau richtig erarbeitet. Die Benamung ist falsch, weil es ja keine Klasse ist, die du uebergibst. Sondern ein Objekt dieser Klasse. Ein besserer Name waere also zB app. Aber das Prinzip, Abhaengigkeiten an den Konstruktor zu uebergeben (oder auch Methoden, an anderer Stelle), ist schon genau das richtige. Und hat eine Vielzahl von Vorteilen, so kann AnotherClass auch mit einem anderen Objekt, das die gleiche Schnittstelle anbietet, funktionieren. ZB im Rahmen eines Tests, den man schreibt.

Und das Argument, dass sei aber kompliziert, ist nicht schluessig. Parameter sind nicht kompliziert, und wenn sie es waeren, warum sind dann Funktionsparameter weniger kompliziert? Das waere dann eben doch ein Plaedoyer fuer globalen Zustand allerorten. Spart man sich so schoen viel Argumente zu uebergeben! Wenn dir das Argument bei Funktionen eingaengig ist, warum dann nicht bei Objekten?
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

@__deets__: Super! Danke für das Feedback, dann scheine ich langsam auf dem rechten Weg zu sein.
Ja - du hast recht. Warum erscheint es mir kompliziert? Vielleicht, weil ich (tief in meinem Inneren) denke, dass ich ja ein ganzes Objekt übergebe und nicht nur die Referenz? Weil ich Sorge habe, so unnötige Daten zu generieren? Weil mein Gehirn Schrittweise denkt, deterministisch - auf A folgt B folgt C. Weil ich mir EINEN Roboter vorstelle, der die Befehle nacheinander ausführt (oder einen Zeiger, der von Zeile zu Zeile wandert) und mich mit mehreren Robotern (sprich Objekten) schwertue, die miteinander interagieren sollen?
Ganz ehrlich: Ich weiß nicht, warum ich diesen Respekt vor dem Konzept der OOP habe, aber immerhin das weiß ich: Damit bin ich nicht allein. Vielleicht fällt es manchen Menschen schwerer als anderen, sich in diese Welt hineinzudenken?
Aber du siehst, ich bemühe mich, und werde es (hoffentlich) hinbekommen. Ich glaube an den Mehrwert dieses Konzepts und gehe (mit kleinen Schritten) weiter.
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

@__deets__: Hmm. Gerade noch einmal nachgedacht. Ich glaube, mein Problem war, dass das Objekt app (natürlich nicht die Klasse - das war mir klar) ja die Objekte (ohne Namen, da einfach in Liste gehalten) selbst erstellt. Diesen Objekten nun sich selbst zu übergeben bzw. die Referenz zu sich selbst, war für mich zunächst nicht einleuchtend. Darüber habe ich dann erst beim Erstellen des Mini-Beispiels für das Forum hier gegrübelt und gedacht, dass das womöglich die Lösung wäre. Ich habe aber die Hoffnung, dass nun der Knoten geplatzt ist, und ich mein Projekt nun sauber weiter in ein "echtes" OOP-Beispiel umfunktionieren kann.
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

@__deets__: Was mich womöglich auch irritiert, ist, dass VS Code bei solch übergebenen Objekten dann nicht mehr auf die Methoden zugreifen kann (also diese in der Liste anzeigt - ich hoffe, du weißt, was ich meine). Das gab mir das Gefühl, dass ich vielleicht mit der Instanzierung und dem Übergeben von Objektreferenzen etwas falsch mache. Aber wahrscheinlich ist das im Editor unvermeidlich. Kann das PyCharm womöglich besser? Aber VS Code gefällt mir ziemlich gut. *grübel*
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist leider eine von diesen Dingen, bei denen die um sich greifenden Typannotationen ein Problem darstellen. Denn die IDEs bauen darauf dann dieses Vorschlagswesen auf, was erstmal grossartig ist. Bis es eben nicht mehr geht. Denn du hast keinen Typ definiert fuer dein Argument, also kann die IDE das nicht.

Man kann das durchaus loesen, wahrscheinlich hier sogar recht einfach durch ein "app:App" - wirklich geil ist das aber nicht. Denn das macht aus dem, was da uebergeben wird, eingentlich konzeptionell schon viel zu viel.

Fakt ist: dass die IDE das nicht kann (und ich glaube nicht, dass PyCharm das besser kann, aber kann mich da irren), hat keinen Einfluss auf die Richtigkeit hier. Das ist fundamental eine Einschraenkung der dynamischen Typisierung von Python.
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Das mit app:App ist ein toller Hinweis. Das werde ich einmal ausprobieren. Ich finde es schon praktisch, die Funktions- /Methodennamen vorgeschlagen zu bekommen.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Onomatopoesie: Anmerkung zu Begrifflichkeiten: Du hast (vorher) nicht funktional programmiert, sondern prozedural. Funktional bedeutet nicht einfach das man Funktionen definiert, sondern unter anderem, dass die auch ohne Nebeneffekte arbeiten, also auch keinen Zustand verändern der ausserhalb der Funktion sichtbar ist, also insbesondere keine globalen Variablen.

Python bietet als multiparadigmische Sprache Syntax und Module um funktional zu programmieren, aber man wird bei den meisten Programmen nicht rein funktional programmieren und GUIs schon gar nicht, weil die Rahmenwerke dafür alle mit veränderbaren Zuständen arbeiten.
Ich muss auf Attribute innerhalb der Klasse zugreifen. Kein Problem. Was aber, wenn ich auf Attribute einer anderen Klasse zurückgreifen möchte? Mit der Klassenvariablen-Methode war das ein Kinderspiel, aber bei einer strengen Objektbehandlung stehe ich (noch?) auf dem Schlauch.
Du vermischst hier Instanzattribute und Klassenattribute und nennst beides Attribute von Klassen.

Ein Problem mit dem ersten Beispiel ist das Erzeugen von dem `Tk()`-Objekt was beim importieren des Moduls erstellt wird, und ein recht krasser Effekt ist, das nur beim importieren ein Hauptfenster erzeugt und auf einigen Plattformen dann auch direkt angezeigt wird. Das setzt ein GUI-System rein für das importieren voraus. Man kann da also keine Werkzeuge verwenden, die das Modul importieren aber beispielsweise auf einem Server ohne grafische Oberfläche liegen. Zum Beispiel Unit-Tests oder Dokumentation aus Modulen ziehen.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

@__deets__: Jawoll. Das war die Lösung. app:App hilft VS Code auf die Sprünge. Ich verstehe deine "Kritik", denn nun ist der Gedanke, dass auch ein anderes Objekt (also nicht mehr instanziert von App, sondern von Bongo z. B.) übergeben werden kann, da VS Code ja nun auf ein App-Objekt hofft. Aber in meinem Fall weiß ich ja sicher, dass ein App-Objekt übergeben werden soll, also ist es für meinen Kontext die richtige Lösung. Danke vielmals!
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

@BlackJack:
Die Sache mit der Begrifflichkeit ist mir auch schon aufgefallen. Ich hatte es in meinem Post vom 04.05. schon einmal als Nebenfrage formuliert.
Du hast natürlich recht - in diesem Fall wäre es am einfachsten, einfach funktional (oder sagt man auch in Python "prozedural"?) zu arbeiten.
Ich war mir also dessen bewusst, dass ich prozedural gearbeitet habe, aber wusste nicht, ob man bei Python auch davon spricht (da es ja nur Funktionen gibt). Den direkten Unterschied zwischen Prozeduren und Funktionen kannte ich aber nicht. Ich habe nicht Informatik studiert (was wahrscheinlich schon deutlich geworden ist), sondern bin bekennender Hobbyist. 8) Für mich war es ein Synonym, aber nun weiß ich es besser. Danke für die Erklärung.

Viel schlimmer noch als meine Unkenntnis über Prozedur vs. Funktion: Was genau meint "Zustand"? Einfach die Änderung einer Variablen? Ist das dann schon eine Zustandsänderung? :oops:

Attribute: Ja - die Terminologie war unsauber bzw. sogar falsch. Da hast du recht. Dass es ein Unterschied ist, habe ich aber verstanden. :)

Du meinst root = tk.Tk() als Problem, richtig? Ich habe verstanden, dass die Klasse sofort beim Initialisieren ausgeführt wird, die __init__-Methode aber nur beim Erstellen von Objekten. Da ich aber keine __init__-Methode brauchte, weil ich ja kein Objekt erstellen wollte, fand ich es naheliegend, das Fenster sofort zu erstellen, weil das schließlich das Ziel ist. Dieses Programm soll mit tkinter arbeiten - für keinen anderen Einsatzzweck ist es konzipiert. Wäre es denn im ersten Beispiel auch von Vorteil gewesen, einfach von tk.Tk zu erben? Und wenn ja, warum?
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Eine Funktion, die mir das Objekt zurückgibt, das ein Objekt (einer anderen Klasse) erstellt hat, gibt es wohl nicht? Also etwas Vergleichbares wie super() bei den Klassen. Dann könnte ich auch etwas schreiben wie: parent(self).summe += 1
Ich konnte bei Google dazu nichts finden, aber vielleicht habe ich auch die Falsche Anfrage gestellt?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, sowas gibt es nicht. Man kann sich Objekthierarchien bauen, und du könntest das über eine Basisklasse erzwingen, dass du eine Hierarchie von Objekten aufbaust, und die traversierende kannst. Ich würde davon ohne guten Grund abraten. Es ist eine der Ursünden in unserer GUI-Anwendung, dass man das so gemacht hat, und damit eine enge Kopplung von Kindobjekten an irgendwelche Urahnen herbeigeführt. Das beißt uns andauern hart in den Allerwertesten. Übergib deine Abhängigkeiten explizit, und nur da, wo gebraucht. Vermeide lange Ketten von objekt.Attribut.Attribut.Attribut (oder auch entsprechende Methodenaufrufe)
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Onomatopoesie: Das mit Funktionen und Prozeduren ist so eine Sache. Wie ”synonym” das ist, hängt von der Programmiersprache ab. Es gibt Sprachen die explizit zwischen Funktionen und Prozeduren unterscheiden. Pascal beispielsweise. In Python oder C gibt es nur Funktionen. In
allen drei Sprachen wird aber bei Funktionen keine rein funktionale Programmierung erzwungen, sondern man kann auch in den Funktionen prozedural programmieren. Und tut das auch oft.

Bei vielen BASIC-Dialekten gibt es im Grunde die gleiche Unterscheidung wie bei PASCAL, nur dass es dort dann nicht Prozedur sondern Unterprogramm heisst. Also statt FUNCTION und PROCEDURE heissen die Schlüsselworte dann FUNCTION und SUB. In beiden Sprachen kann man aber auch Werte die ausserhalb sichtbar sind, in beiden Konstrukten verändern. Im Grunde wird nur unterschieden ob das was man da aufruft einen Rückgabewert hat oder nicht.

Das kann man in C auch festlegen, trotzdem werden ”Funktionen” die keinen Rückgabewert haben, dort auch Funktionen genannt. Und in Python gibt es keinen Aufruf ohne Rückgabewert. Wenn man explizit nichts zurück gibt, wird implizit `None` zurück gegeben.

Zustand wird üblichweise mit Variablen modelliert. Also wenn ein Name je nach dem wann man den zu einem Wert auflöst einen anderen Wert hat, dann hat sich da Zustand geändert. (Rein) Funktional programmiert gibt es so etwas nicht, da wird der gleiche Name im gleichen Namensraum immer für den gleichen Wert stehen. Also Variablen im mathematischen Sinn beliebig aber fest. Man kann ein beliebiges `x` wählen, aber wenn man das erst einmal gewählt hat, dann steht das `x` auch für die gesamte Auswertung für den einen gewählten Wert.

Du kannst im ersten Beispiel ja mal von `Tk` erben und schauen was das für Änderungen und Probleme nach sich zieht. Entweder Du landest dann beim zweiten Beispiel oder bei etwas das noch verwirrender ist als das erste Beispiel.

Was ist denn ein Objekt das ein anderes Objekt erstellt hat? Also welcher Kontext wird da verwendet? Objekt erstellen passiert durch einen Aufruf. Der kann in einem Modul, in einer Funktion, in einer Methode stehen. Die Funktion oder Methode kann wiederum Attribut eines Objekts sein. Dieses Objekt kann eine Klasse sein, ein Modul, ein Objekt das keine Klasse ist, wo die Methode in der Klasse definiert wurde aus der das Objekt erzeugt wurde, aber auch auch nicht, denn Methoden sind ja auch Objekte, also Werte, die man übergeben kann und auch als Attribut auf anderen Objekten speichern kann. Was genau soll `parent()` in diesen ganzen Konstellationen jeweils liefern? Und es müsste *jedes* Objekt irgendein Elternobjekt speichern. Wie nützlich ist das?

Zudem bekommt man Probleme wenn man dann anfängt Code aus einer Methode heraus zu ziehen, oder in eine andere Klasse zu verschieben, weil sich damit dann ändern könnte wo letztlich ein Rückgabewert erzeugt wird. Du weisst aber beim schreiben so einer Methode nicht welcher Code die aufruft und sich darauf verlässt was `parent()` liefert. Beziehungsweise müsstest Du in solchen Fällen dann überall den aufrufenden Code kennen.

Konkret verstehe ich auch nicht was ``parent(self).summe += 1`` bedeuten soll. Also ich weiss natürlich schon was Du da gerne als Effekt haben möchtest, aber warum ist in dem Beispiel dann ``parent(self)`` das `App`-Exemplar? Warum nicht irgend etwas anderes? Wo, an welcher Stelle im Code würde klar, dass diese Eltern/Kind-Beziehung zwischen dem `App`-Exemplar und `SomeClass`-Exemplar besteht? Das müsste die Laufzeitumgebung ja irgendwoher wissen.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten