Programmierstil

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

@Perlchamp: ›main‹ ist ja auch eine Methode von `Application`. Du mußt also erst eine `Application`-Exemplar erzeugen, um dann main aufzurufen um dann darin noch ein `Application`-Exemplar zu erzeugen. Das macht keinen Sinn.
`main` muß eine Funktion sein.
Dass Du implizit ein Tk-Objekt erzeugst ist unschön. Ein Widget sollte sich nicht selbst Anordnen, denn so kannst Du den Frame nur mit grid benutzen, nicht mit pack.
Methoden schreibt man wie Funktionen und Variablen klein_mit_unterstrich.
`createWidget` ist eigentlich auch nicht nötig, weil man das direkt in __init__ machen kann.
Dass man `grid` ohne Parameter aufrufen kann, heißt nicht, dass man das auch machen sollte.

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.quitButton = tk.Button(self, text='Quit',
            command=self.quit)
        self.quitButton.pack()

def main():
    root = tk.Tk()
    root.title("ROOT_TITLE")
    app = Application(root)
    app.pack()
    root.mainloop()

if __name__ == '__main__' :
    main()
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sirius3:
vielen Dank für deine Antwort. Ich habe das Beispiel original aus der Tkinter 8.5 Doku von der Mexican Tech. Es ist doch zum Ko ....
Anhand MEINES Codes bei meiner Anfrage wirst du gesehen haben, dass ich zumindet alle Bezeichner und Methoden/Funktionen richtig geschrieben habe. Auch die row- und column-Angabe in grid hatte ich, aber darauf will ich nicht hinaus.
Wo gibt es denn eine Doku/Manual/Tutorial, wo man auch gutes Python lernen kann? Ist doch blöd, wenn man sich zu Anfang *schreckliche* Dinge angewöhnt, die man dann nur noch schwer losbekommt.
Fragen:
Dass Du implizit ein Tk-Objekt erzeugst ist unschön.
du beziehst dich damit auf *self.createWidgets()* ?
Also am besten alles (Widgets, Layout-Manager) unterhalb des Klassenkonstruktors *def __init__ (self, ...)* packen ? (als quasi-Faustregel)
Ein Widget sollte sich nicht selbst Anordnen [...]
du beziehst dich damit auf *self.grid()* ?
Du benutzt in deinem Code ausschließlich den Packer. Bist du ein Freund von pack() ? In der Doku der Mexican Tech steht u.a.:
Your application class must inherit from Tkinter's Frame class. In Tkinter, the Frame widget is the basic unit of organization for complex layouts. A frame is a rectangular area that can contain other widgets.

Although there are three different “geometry managers” in Tkinter, the author strongly prefers the .grid() geometry manager for pretty much everything. This manager treats every window or frame as a table—a gridwork of rows and columns.
Stimmt das alles, oder muß ich wieder etwas anderes lernen ?

unklar ist mir auch noch das *.master* im Originalcode.

Code: Alles auswählen

root.master.title(ROOT_TITLE)
ich bin noch etwas verwirrt, und DEIN Code "klingt" wirklich einleuchtend, danke dafür, sirius3 :-)
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sirius3:
so, ich habe jetzt etwas mit deinem Code herumgespielt und habe diesbezüglich noch ein paar Fragen mehr. Mein jetziger Code lautet wiefolgt:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''test.py
  tkinter mittels Klassendefinition
  + grafische Benutzeroberfläche (GUI)
  + Erstellen eines quit-Buttons
  + Layout-Manager (grid)
  '''

__author__ = 'Perlchamp'
__date__ = '06.03.2019'


import tkinter as tk

##FAVICON = 'image/perlchamp_20x16.ico'
ROOT_TITLE = ' Button mittels Klassendefinition'
ROOT_WIDTH = 350
ROOT_HEIGHT = 200

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.quit_button = tk.Button(self, text='Quit', command=self.quit)
        self.quit_button.grid(row=0, column=0)

def main():
    root = tk.Tk()
    root.title(ROOT_TITLE)
##    root.iconbitmap(FAVICON)
    root.geometry(f'{ROOT_WIDTH}x{ROOT_HEIGHT}')
    app = Application()
    app.grid(row=0, column=0)
    root.mainloop()

if __name__ == '__main__' :
    main()
wird in der Zeile

Code: Alles auswählen

 tk.Frame.__init__(self, master)
tk.Frame ÜBERLADEN ?
ist in der Zeile

Code: Alles auswählen

self.quit_button = tk.Button(self, text='Quit', command=self.quit)
quit eine build-in-Funktion der tkinter-Klasse ?
was ist der Unterschied in *def main()* zwischen

Code: Alles auswählen

app = Application(root)  # Frage: Tk()-Instanziierung (sagt man das so ?)
und

Code: Alles auswählen

app = Application() # Frage: tk.Frame-Instanziierung ?
funktioniert ja beides, ausgehend von meinem Wissensstand/Test.

EDIT:
anstelle von

Code: Alles auswählen

root.mainloop()
könnte ich ja auch

Code: Alles auswählen

app.mainloop()
schreiben, Worin genau liegt hier der Unterschied ?
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Perlchamp hat geschrieben: Mittwoch 6. März 2019, 21:28 wird in der Zeile

Code: Alles auswählen

 tk.Frame.__init__(self, master)
tk.Frame ÜBERLADEN ?
Überladen bedeutet, dass der Compiler abhängig von Typ und Anzahl der Argumente eine spezifische Funktion oder Methode aufrufen soll. Dies liegt hier nicht vor und wird in Python auch nicht unterstützt. Was man im vorliegenden Code sieht, ist das Überschreiben einer Methode durch die erbende Klasse und der Aufruf der gleichnamigen Methode aus der Elternklasse. Der Aufruf ist nötig, da Python sonst nur den Code aus der neuen "Version" von __init__() ausführen würde und somit alles aus der alten __init__() ignorieren würde.

Auf die anderen Fragen will ich nicht eingehen, da meine TKinter-Kenntnisse eher oberflächlich sind...
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Tk/Tcl ist eine Skriptsprache, die sehr viel Dinge automatisch macht, um schnell mal in wenigen Zeilen ein Fenster zu machen. Es arbeitet viel mit globalen Zuständen und Funktionen. Die Tkinter-Anbindung an Python hat sich dazu entschieden, viele globale Funktionen als Methoden direkt an die Widgets zu binden, also mainloop, after, etc. die aber gar nicht auf dem jeweiligen Widget operieren. Das kann verwirrend sein.

Das Hauptfenster existiert (oder wird bei Bedarf erzeugt) als globale Variable. Wenn man also nichts angibt, wird automatisch das Hauptfenster genommen.
Das ist aber schlechter Stil. Um wenigstens in Python so zu tun, als ob das Hauptfenster lokal erzeugt wurde, sollte man es immer beim Erzeugen von anderen Widgets als Parent übergeben, statt es einfach wegzulassen. Ebenso ist es guter Stil, nur einen mainloop zu haben, der vom Hauptfenster aus aufgerufen wird.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ snafu:
deine Antwort verwirrt mich etwas, denn:
Ein wichtiges Konzept der objektorientierten Programmierung ist der Polymorphismus (bzw. die Polymorphie). Damit ist die Möglichkeit gemeint, den gleichen Namen für (mehr oder weniger) gleichartige Operationen zu verwenden, die auf Objekte unterschiedlicher Klassen angewendet werden. Man spricht auch vom Überladen (overloading) einer Operation.
Bei Python können Operatoren und Standardfunktionen überladen werden, indem man bei einer neuen Klasse bestimmte Methoden mit vorgegebenen Namen definiert. Diese reservierten Methodennamen beginnen und enden mit doppelten Unterstrichen. [...]
Und ich gehe davon aus, dass du genau das gemeint hast ...
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sirius3:
ok, das ist in der Tat verwirrend, aber ich denke, dass ich dies soweit verstanden habe, muss es aber zuvor noch sacken lassen. Es ist schade, dass dies in vielen Tuts ignoriert wird, was es mir schwerer macht ... nun gut, einen Tod muss ich wohl sterben ...
Danke für deine Antwort und deine sehr gute Erklärung ...
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Perlchamp hat geschrieben: Donnerstag 7. März 2019, 21:02 @ snafu:
deine Antwort verwirrt mich etwas, denn:

[...]

Und ich gehe davon aus, dass du genau das gemeint hast ...
"Überladen" meint in der Programmierung normalerweise das Überladen von Methoden oder Funktionen, so wie snafu geschrieben hat.
Dein Textauszug, den du aus dem Zusammenhang reißt (stammt übrigens aus "Python 3: Lernen und professionell anwenden. Das umfassende Praxisbuch" von Michael Weigend) meint aber - wie das auch in dem Text steht - das Überladen von Operatoren. Das ist etwas anderes.

Übrigens ist deutsche Literatur im Bezug auf Python in der Regel ... schwierig bis falsch. Dieses Buch kenne ich allerdings nicht.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sparrow:
__add__ beispielsweise ist ein Operator, richtig. Ich 'rede' ja in dem Textauszug von Operatoren UND Funktionen (und somit auch Methoden).
Folgende Passage von snufu hat mich verwirrt:
Überladen bedeutet, dass der Compiler abhängig von Typ und Anzahl der Argumente eine spezifische Funktion oder Methode aufrufen soll. Dies liegt hier nicht vor und wird in Python auch nicht unterstützt.
Im Grunde weiß ich ja, was gemeint ist/war, und möchte mich daran jetzt auch nicht aufhängen ...
Ich wollte ja nur wissen, ob dies durch

Code: Alles auswählen

tk.Frame.__init__(self, master)
der Fall ist ...
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: `__add__()` ist eine Methode. ``+`` ist ein Operator. Mittels `__add__()` kann man in Python den ``+``-Operator überladen. Bei Methoden geht das in Python nicht, weil sich das Überladen auf den gleichen Namensraum bezieht und es kann jeden Namen immer nur einmal pro Namensraum geben.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Perlchamp: __init__() ist wie __add__() eine sogenannte Dunder-Methode (wegen den beiden Unterstrichen) und beide haben eine spezielle Bedeutung. Das Überschreiben von __init__() steuert aber nicht das Verhalten von irgendeinem Operator. Welcher sollte das auch sein?

Ich schrieb übrigens explizit vom Überladen von Methoden / Funktionen, nicht von Operatoren. Wenn man beides in einem Satz nennt, ist wahrscheinlich die Terminologie noch nicht ganz klargeworden. Funktionen kommen in Mathe ja eigentlich spätestens in der Oberstufe dran und den Begriff Operator hat man dann vielleicht auch schonmal gehört... :)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielleicht nochmal, um die Verwirrung hoffentlich zu beseitigen: Über*laden* ist etwas anderes als Über*schreiben*. Und Überladen von *Methoden* ist etwas anderes als Überladen von *Operatoren*.

Für das Überladen von Operatoren hat man in Python spezielle Dunder-Methoden: __add__() für den Additionsoperator (+), __mul__() für das Mal-Zeichen (Multiplikation), usw. Denen darf man nach Belieben neues Verhalten zuweisen. So könnte man Plus und Minus vertauschen, wenn es einem Spaß macht. Oder aber - in ernsteren Zusammenhängen - das Größer-Als Zeichen z.B. für Umleitungen nutzen, ähnlich wie man das von der Shell kennt.

Überladen von Methoden heißt etwas ganz anderes. Nämlich dass anhand von Parameteranzahl und Parametertyp eine jeweils eigene Variante der Methode aufgerufen wird. Wenn man also foo(x) und foo(x, y) definiert hat, dann kennt der Compiler zwei foo's und ruft das jeweils passende foo() bei Übergabe von einem oder von zwei Parametern auf. Bei drei oder null Parametern gäbe es einen Fehler. Python unterstützt aber nur foo(x) oder foo(x, y). Beides zusammen geht nicht. Dann würde die zuletzt gemachte Definition die ältere überschreiben.

Und dann sind wir beim Thema Überschreiben. Das heißt einfach, dass die überschriebene Funktion oder Methode von der Neuen überdeckt wird. Im Falle von Vererbungen kommt man dennoch an die ursprüngliche Methode, wenn man sie über die Elternklasse aufruft. Das findet man häufig bei Vererbungen, dass man quasi das Original benutzen möchte, aber etwas hinzufügt (z.B. Loggen von Fehlern).

Und nein, du wusstest offenbar nicht, was ich meinte und hast mir Worte in den Mund gelegt, die einen ganz anderen Kontext haben, da du eben nicht den Zusammenhang verstanden hast. Ist hoffe aber, nun sind die Unterschiede klarer geworden. ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei das Beispiel mit dem Überladen von `foo` ein bisschen unvollständig ist, denn das war ja nur mit der Anzahl der Argumente. Die muss für's überladen gar nicht unterschiedlich sein. Man kann auch die gleiche Anzahl, aber unterschiedliche Typen haben. Dafür bräuchte man dann aber Typdeklarationen und einen Compiler der je nach Typ die entsprechende Funktion wählt.

Wenn man sich beispielsweise mal `bytes()` anschaut, dass wäre etwas, was man in statisch typisierten Sprachen die Überladen unterstützen mit vier oder fünf verschiedenen überladenen Definitionen lösen würde. Ein Beispiel das nur mit der Anzahl der Argumente auskäme, wäre `range()`.

Damit Überschreiben mit Überdecken/Verdecken gleichzusetzen wäre ich vorsichtig. Da kommt man in Python mit durch, aber es gibt Sprachen die einen Unterschied machen, ob man eine Methode überschreibt oder überdeckt/vedeckt. Überschreiben braucht/erzeugt eine ”virtuelle” Methode und einige Sprachen erfordern dann auch das man explizit ein Schlüsselwort wie ``overriding`` verwendet um klar zu stellen das man überschreibt, und nicht verdeckt, was man ohne das Schlüsselwort tun würde. Das ist jetzt genau wie das Überladen in Python natürlich schlecht anschaulich zu erklären, weil in Python grundsätzlich alle Methoden ”virtuell” sind.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

__blackjack__ hat geschrieben: Freitag 8. März 2019, 10:12 Wobei das Beispiel mit dem Überladen von `foo` ein bisschen unvollständig ist
Ich bin beim Beispiel nicht weiter auf die Typunterscheidung eingegangen, da dies in Python nicht relevant ist. Genannt hatte ich den Punkt aber bereits.
__blackjack__ hat geschrieben: Freitag 8. März 2019, 10:12 Damit Überschreiben mit Überdecken/Verdecken gleichzusetzen wäre ich vorsichtig. Da kommt man in Python mit durch, aber es gibt Sprachen die einen Unterschied machen, ob man eine Methode überschreibt oder überdeckt/vedeckt.
Und das macht dann genau welchen Unterschied im Verhalten? Dient das nicht nur dafür, dem Compiler mitzuteilen, dass hier bereits eine gleichnamige Methode existiert, also dass er warnt wenn diese wider Erwarten doch nicht existiert (z.B. weil man sich vertippt hat) bzw umgekehrt nicht anmeckert, dass man einen bereits vergebenen Namen für die Methode gewählt hat? So gesehen wäre Überschreiben dann halt "bewusstes Überdecken"...
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: Die Frage ist welche Methode aufgerufen wird wenn man einen Typ als einen Basistyp behandelt. Also ob dann die überschriebene Methode des Typs aufgerufen wird, oder die verdeckte Methode.

Im folgenden C++-Beispiel wird `foo()` überdeckt und `bar()` überschrieben:

Code: Alles auswählen

#include <iostream>

using namespace std;

class Base
{
public:
    void foo() { cout << "Base foo" << endl; }
    virtual void bar() { cout << "Base bar" << endl; }
};

class Sub : public Base
{
public:
    void foo() { cout << "Sub foo" << endl; }
    virtual void bar() { cout << "Sub bar" << endl; }
};

int main()
{
    Sub sub;
    sub.foo();
    sub.bar();
    
    Base &base = sub;
    base.foo();
    base.bar();

    return 0;
}
Die Ausgabe davon ist:

Code: Alles auswählen

Sub foo
Sub bar
Base foo
Sub bar
Wenn man ein `Sub`-Objekt als `Base`-Objekt behandelt, wird trotzdem die in `Sub` überschriebene Methode aufgerufen. Bei der in `Sub` bloss verdeckten Methode wird aber die Implementierung in `Base` verwendet.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Okay, leuchtet mir nun ein. Danke für die Erklärung. :)
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ alle:
es ist schwierig für einen Anfänger OHNE Weitblick da komplett durchzusteigen, zumal ich gerade erst dabei bin, mich mit Klassen auseinanderzusetzen. Was ich (hoffentlich) verstanden habe ist, dass man beispielsweise eine bestehende (build-in-)Funktion/Methode überschreiben, sprich (nach seinen Vorstellungen oder nach skriptbedingter Notwendigkeit) modifizieren kann, ohne die Ürsprungs-Methode/Funktion zu verändern. Praktisch (nicht faktisch !) gleichzusetzen mit einem Objekt (z.B. einer Liste), an das man eine Botschaft sendet, dessen Ergebnis/Auswertung man dann weiter verwertet. Das Original-Objekt bleibt dabei unangetastet/unverändert. Ich weiß, dass dieser Vergleich *hinkt*, aber so ist das nunmal mit Anfängern ...
Die Falle, in die ich (durch meine Argumentation und mein Anfängerwissen) getappt bin, war, dass ich glaubte,

Code: Alles auswählen

tk.Frame.__init__(self, master)
könne man mit

Code: Alles auswählen

def __init__(self, master=None)
gleichsetzen. Dabei ist

Code: Alles auswählen

def __init__(self, master=None)
im Grunde genommen eine Über*ladung* des Zuweisungs*operator*s '=', der verwendet wurde, um eine Instanz einer Klasse zu gernerieren/modifizieren, während

Code: Alles auswählen

tk.Frame.__init__(self, master)
eine Über*schreibung* ist, danke nochmals für die Aufklärung !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Es ist spät, aber das klnigt nicht korrekt.

Dieser Vergleich mit der Liste hinkt. Listen beinhalten Elemente und haben nichts mit Botschaften zu tun.

Dann verwendest du das Wort "gleichsetzen" und sprichst im nächsten Moment vom "Überladen".
Nein.

tk.Frame.__init__(self, master) -> Ruft die Funktion __init__ der Klasse tk.Frame auf und übergibt die Parameter self und master.
def __init__(self, master=None) -> Ist der Funktionskopf der Funktion "__init__" und definiert die zu übergebenen Parameter "self" und "master", wobei der Default-Wert von "master" None ist, sollte er nicht übergeben werden.

snafu hat doch schon geschrieben, was passiert:
snafu hat geschrieben: Mittwoch 6. März 2019, 23:55Was man im vorliegenden Code sieht, ist das Überschreiben einer Methode durch die erbende Klasse und der Aufruf der gleichnamigen Methode aus der Elternklasse. Der Aufruf ist nötig, da Python sonst nur den Code aus der neuen "Version" von __init__() ausführen würde und somit alles aus der alten __init__() ignorieren würde.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@sparrow :
ich geh pennen, gute nacht
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Ich glaube, du hast dich daran festgebissen, dass du jetzt in allem, was vorne und hinten zwei Unterstriche hat, einen überladenen Operator siehst. Dem ist nicht so. Sie kennzeichnen Funktionen, die in bestimmten Situationen vom Kompiler aufgerufen werden. __init__ ist, was vom Compiler bei der Instanzierung einer Klasse aufgerufen wird. Der Konstruktor zur Initialisierung.

Wenn du die offizielle Dokunentation durcharbeitest, kommst du irgendwann hier vorbei. Da sieht man, dass es für Objekte einige dieser Funktionen gibt.
Antworten