Problem mit Button verständnis

Fragen zu Tkinter.
Antworten
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Hallo Zusammen,

ich bin Python anfänger und versuche mich gerade in das Thema der GUI einzuarbeiten..
Im folgendem Beispiel Code stellt sich mir die Frage:

Angenommen ich drücke den Button 1 (zu diesem Zeitpunkt befinden wir uns bei Zeile 19, dann wird das Dictionary Label mit dem Wert "cheeck1" befüllt.
In Zeile 26 wird das Dictionary "Label" allerdings mit dem Wert "result" überschrieben.

Wie kommt es also nun zustande das trotzdem "cheeck1" ausgegeben wird ?

http://bpaste.net/show/8f3R073IXaBdLLHgbRZs/

Bitte um hilfe und danke im Vorraus!

Gruß, Kevin
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du irrst Dich in Sachen "Zeitpunkt". Das besondere bei GUI-Applikationen ist es, dass diese eben nicht starr sequentiell ablaufen, sondern Code Ereignis gesteuert ausgeführt wird.

In den Zeilen 19 und 22 werden eben nicht die Funktionen ``out1`` und ``out2`` aufgerufen, sondern sie (also die Funktionsobjekte!) werden an die Button-Objekte übergeben. Aufgerufen werden sie erst zu einem beliebigen Zeitpunkt, eben wenn der Button gedrückt wird.

Hier mal ein Nicht-GUI Beispiel zur Verdeutlichung:

Code: Alles auswählen

container = list()

def foo(container):
    container.append("Ich bin in ``foo``")

class Button():

    def __init__(self, func, container):
        self.func = func
        self.container = container

    def __call__(self):
        self.func(self.container)

# wir übergeben das Funktionsobjekt an ein anderes Objekt
button = Button(foo, container)

container.append("Ich werde zuerst angehängt")

# und jetzt erst rufen wir das Funktionsobjekt auf!
button()
Zeile 26 wird zwar nach dem "Binden" der Funktionen an die Buttons, aber *vor* dem Eintritt in die Event-Schleife (``main.mainloop``) ausgeführt.

In dieser Schleife läuft Dein GUI-Programm im eigentlichen Sinn ab. Du kannst Dir das wie eine unendliche Schleife vorstellen, in der alle möglichen Ereignisse behandelt werden, wie eben der Druck auf einen Button.

Analog wird in meinem Beispiel das Funktionsobjekt *vor* dem direkten Aufruf von ``list.append`` an ein Button-Objekt übergeben; aufgerufen wird es aber eben erst danach.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Danke für deine ausführliche Erklärung!

Dann habe ich es so richtig vertanden?

Bis zur Zeile 32 sind es eigentlich alles "Definitionen". Das Programm läuft bis zu Zeile 32 durch, "bleibt" dort und führt alle events aus die zutreffen (zb. Button drücken).
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

kevind hat geschrieben: Bis zur Zeile 32 sind es eigentlich alles "Definitionen". Das Programm läuft bis zu Zeile 32 durch, "bleibt" dort und führt alle events aus die zutreffen (zb. Button drücken).
Nein. Der Interpreter geht das Modul schon von oben nach unten durch und führt Code auch aus! Schon beim ``import`` fängt es an; das wird schon ausgeführt. Bei den Funktionsdefinitionen wird der Code der Funktion natürlich nicht ausgeführt, sondern Python legt nur das Funktionsobjekt an.

Ab Zeile 19 hingegen steht tatsächlich nur Code, der auch sofort ausgeführt wird.

Das entscheidende ist, dass es *keinen* Aufruf der Funktionen gibt! Das erledigt eben eine Funktion in der Button-Klasse, die Du gar nicht "siehst". In der ``mainloop`` werden eben Events den zugehörigen GUI-Elementen zugeordnet und dann aufgerufen.

Beachte, dass ein Funktionsaufruf durch die runden Klammern kenntlich gemacht wird. ``out1()`` ruft die Funktion (bzw. genauer das Callable), die an den Namen ``out1`` gebunden ist, auf. Mittels ``out1`` hingegen greift man schlicht auf das Objekt "hinter" dem Namen zu. In Python ist gerade alles ein Objekt, was man an einen Namen binden kann. Da das für Funktionen genauso gilt wie für Klassen, kannst Du diese "fröhlich" als Parameter herumreichen usw. Genau das wird beim Anlegen eines Tk-Button Objektes ausgenutzt, damit Du Funktionalität an den Button binden kannst.

Hier noch einmal ein Beispiel:

Code: Alles auswählen

from functools import partial

class Button(object):

    def __init__(self, command):
        # unsere Funktionalität, die wir mit einem Button-Objekt
        # verknüpfen wollen
        self.command = command

    # Wir machen ein Callable aus unserer Klasse
    # (Damit wird der Aufruf hübscher und wir legen uns nicht auf
    # einen speziellen Typen fest, der in unserem Mapping stehen muss)
    # Wir könnten da nun auch direkt eine Funktion angeben, oder
    # einen lambda-Ausdruck usw.
    def __call__(self):
        return self.command()

# zwei Dummyfunktionen
def foo():
    return "Hello World!"

def bar(param):
    return "Hello, {}!".format(param)

# hier mappen wir zwei Strings auf Button-Objekte, damit wir einen
# Tastendruck über eine Benutzereingabe simulieren können.
# Dabei übergeben wir die Funktionalität von Funktionen an die
# Button-Objekte! Aufgerufen werden sie natürlich noch nicht.
mapping = {"a": Button(foo), "b": Button(partial(bar, "Hyperion"))}

# unsere Mainloop
while True:
    choice = raw_input("Welcher Button soll gedrückt werden? ('a' oder 'b')").lower()
    print "{} -> {}".format(choice, mapping[choice]())
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Okay mit "Nur Definitionen" habe ich mich etwas falsch ausgedrückt.

Von Zeile 1 bis zum Mainloop wird der code jeweils 1 mal ausgeführt. Solange die Destroy Funktion nicht aufgerufen wird bleibt das Programm auch im Mainloop.

So wäre es besser formuliert oder ?

Gruss, Kev
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

kevind hat geschrieben: Von Zeile 1 bis zum Mainloop wird der code jeweils 1 mal ausgeführt.
Naja, Funktionen und Klassen werden ja eben nicht ausgeführt ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Gut Code wird einmalig ausgeführt und gegebenfalls deklariert, so besser :) ?
BlackJack

Deklariert wird in Python (fast) nichts. Die ``def``- und ``class``-Anweisungen werden ausgeführt.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

BlackJack hat geschrieben:Deklariert wird in Python (fast) nichts. Die ``def``- und ``class``-Anweisungen werden ausgeführt.
Hast du gelesen was Hyperion geschrieben hat ? Wem soll ich nun glauben ;) ?
BlackJack

@kevind: Mir natürlich. :-D

``def`` und ``class`` sind ausführbare Anweisungen und wenn der Programmfluss an denen vorbei kommt, werden sie ausegführt.

Bei ``def`` bedeutet das es wird ein Funktionsobjekt mit dem Code für den Funktionskörper erstellt, Ausdrücke für Default-Argumente werden ausgewertet, und das Funktionsobjekt wird an den Namen nach ``def`` gebunden. In dem Namensraum, in dem das ``def`` ausgeführt wird. Also Modul, Funktion, oder Klasse (s.u.). Falls Dekoratoren vorhanden sind, werden diese ausgeführt. Es wird *nicht* der Funktionskörper ausgeführt! Das würde a) keinen Sinn machen und b) bei Funktionen die Argumente (ohne Defaultwerte) erwarten, auch gar nicht so einfach möglich sein.

Bei ``class`` wird ein neuer, vorerst anonymer Namensraum erstellt, und in diesem wird der Code im Klassenkörper ausgeführt. Dabei gilt für ``def``\s das weiter oben gesagte. Nach dem der Code im Klassenkörper ausgeführt wurde, wird der Namensraum als Klassenobjekt an den Namen nach ``class`` gebunden. Eventuell vorhandene Dekoratoren werden ausgeführt. Metaklassen erspare ich uns jetzt mal. :-)

Code: Alles auswählen

#!/usr/bin/env python
# encoding: utf-8


def create_default():
    print '  Funktion `create_default` wird ausgeführt.'
    return 42


print 'Modulebene, vor ``def``.'

def function(argument_a, argument_b=create_default()):
    print '  Funktion `function` wird ausgeführt.'
    print '    Argumente:', argument_a, 'und', argument_b

print 'Modulebene, nach ``def`` und vor ``class``.'

class Class(object):
    print '  Klassenkörper von `Class` vor ``def``.'
    
    def method(self):
        print '  Methode `%s.method` wird ausgeführt.' % self.__class__.__name__
    
    print '  Klassenkörper von `Class` nach ``def``.'

print 'Modulebene, nach ``class``.'


def main():
    print 'Funktion `main` wird ausgeführt.'
    function(23)
    class_ = Class()
    class_.method()


if __name__ == '__main__':
    main()
Interessant ist hier wann `create_default` ausgeführt wird — nämlich nur einmal wenn das ``def`` ausgeführt wird und *nicht* jedes mal wenn die Funktion oder Methode aufgerufen wird. Das macht in diesem Fall keinen Unterschied, aber wenn der Default-Wert ein veränderbares Objekt ist, dann sind einige überrascht, dass bei jedem Aufruf das *selbe* Objekt an den lokalen Namen gebunden wird.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

kevind hat geschrieben: Hast du gelesen was Hyperion geschrieben hat ? Wem soll ich nun glauben ;) ?
Öh... wo bitte hab ich etwas Gegenteiliges behauptet :?:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Okay habs nun verstanden. danke euch !
Antworten