Class Methode in Label aufrufen

Fragen zu Tkinter.
Antworten
Shadow Emperor
User
Beiträge: 1
Registriert: Samstag 2. Januar 2016, 20:24

Hallo an alle,

Falls ihr euch fragt wieso ich so ein unnötiges Programm schreibe, unsere Aufgabe(Schule) ist es, eine Ampel in Tkinter zu erzeugen, welche in der Shell z.B. durch "ampel1 = Ampel()" aufgerufen wird, aus der Klasse Ampel() hervorgeht..
Dies habe ich auch schon gemacht, allerdings finde ich, dass es sich bei einer Ampel dann ja auch lohnen würde sie umschalten zu können.

Also habe ich mich durch die Tutorials dieser Welt gelesen und bin nun zu folgendem Code gekommen:

Code: Alles auswählen

import time
from tkinter import*
# Rot=1 Gelb=2 Grün=3
class Ampel:
    metallfarbe="Teal"
    höhe=200
    breite=75
    aktiveLampe="Rot"
    def HilfsLabel(label):
        counter = 0
        def count():
            global counter
            counter =+ 1
    def methode(self, Aktiv):
        if(Aktiv==1):
            self=Tk()
            BG.place(x=87, y=35, width="75" , height="200")
            Rot=Label(self ,bg="black")
            Aktiv=2
            Rot.place(x=97, y=44, width="55" , height="55")
            Gelb=Label(self ,bg="yellow")
            Gelb.place(x=97, y=108, width="55" , height="55")
            Grun=Label(self ,bg="black")
            Grun.place(x=97, y=172, width="55" , height="55")
            label.config(text=str(counter))
            label.after(1000, count)
            count()
            BG.place(x=87, y=35, width="75" , height="200")
            Rot=Label(self ,bg="black")
            Aktiv=3
            Rot.place(x=97, y=44, width="55" , height="55")
            Gelb=Label(self ,bg="black")
            Gelb.place(x=97, y=108, width="55" , height="55")
            Grun=Label(self ,bg="green")
            Grun.place(x=97, y=172, width="55" , height="55")
            
    def __init__(self):
        global Aktiv
        Aktiv = 1
        self=Tk()
        self.title("Fabian Blitz - Ampel")
        self.config(bg="teal")
        self.geometry('250x290')
        BG=Label(self , bg="DarkGrey")
        BG.place(x=87, y=35, width="75" , height="200")
        Rot=Label(self ,bg="red")
        Rot.place(x=97, y=44, width="55" , height="55")
        Gelb=Label(self ,bg="black")
        Gelb.place(x=97, y=108, width="55" , height="55")
        Grun=Label(self ,bg="black")
        Grun.place(x=97, y=172, width="55" , height="55")
        b1=Button(self, text="Umschalten", bg="DarkGrey", command=methode())
        b1.place(x=87, y=245)
        self.mainloop()

Wenn ich nun in der Shell "ampel=Ampel()" schreibe, bekomme ich die Fehlermeldung, dass die Funktion "methode()" nicht definiert sei.
Ich habe es auch schon mit "methode", "methode(self)", "methode(self, Aktiv)" versucht, allerdings ohne Erfolg.


Nun sitze ich hier und komme an dieser Stelle nicht weiter.

Wäre schön wenn ihr mich helfen könntet.
Zuletzt geändert von Anonymous am Freitag 15. Januar 2016, 19:10, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Shadow Emperor: Da sind viele Fehler drin.

Vergiss ``global``. Das hat in sauberen Programmen nichts zu suchen und wenn Du schon objektorientierte Programmierung (OOP) verwendest gibt es keine Ausrede mehr auf ``global`` zurück zu greifen.

Sternchenimporte sind Böse™. Damit holst Du Dir gerade bei GUI-Rahmenwerken Unmengen von Namen in das Modul die gar nicht gebraucht werden. Man sieht nicht mehr so leicht woher welcher Name eigentlich kommt, und es besteht die Gefahr von Namenskollisionen. Das `tkinter`-Modul wird üblicherweise als `tk` importiert (``import tkinter as tk``) und dann kann man über den Namen `tk` an die Namen aus dem Modul heran kommen. So sieht man sofort was aus diesem Modul kommt und was nicht.

Bezüglich der Namensschreibweisen und Quelltextformatierung lohnt ein Blick in den Style Guide for Python Code.

Die Attribute auf Klassenebene gehören dort nicht hin beziehungsweise sollten durch die Schreibweise als Konstanten kenntlich gemacht werden (sofern es denn Konstanten sind). Konstanten werden per Konvention komplett in Grossbuchstaben geschrieben.

`HilfsLabel()` ist kein guter Name für eine Funktion oder Methode. Die werden üblicherweise nach Tätigkeiten benannt weil sie etwas tun und um sie von ”passiven” Werten unterscheiden zu können. Der Name würde eher zu einem `Label`-Exemplar passen, beziehungsweise in der Schreibweise mit den Grossbuchstaben müsste das eigentlich der Name einer Klasse sein.

Es fehlt auch das `self`-Argument und `label` wird nicht verwendet, womit das semantisch gar keine Methode ist und damit sehr wahrscheinlich auch nicht in die Klasse gehört. Mehr kann man dazu nicht wirklich sagen weil der Inhalt der Funktion kompletter Unsinn ist. Bis hin zu das Du über die Bedeutung von ``counter =+ 1`` mal ganz gründlich nachdenken solltest. ;-)

Der Name `methode()` ist auch schlecht. Das es eine Methode ist, sieht man ja schon daran das es in einer Klasse definiert ist, beziehungsweise das es irgendwo anders im Quelltext dann auf einem Objekt aufgerufen wird. Namen sollen dem Leser vermitteln was der Wert der dahinter steht im Programm bedeutet. Bei Funktionen und Methoden also was sie *tun*.

``if`` ist keine Funktion, sollte also auch nicht so geschrieben werden als wäre es eine. Die überflüssigen Klammern gehören da weg.

`Aktiv` wird als Argument übergeben, ist damit also ein lokaler Name. Zuweisungen an den Namen in der Methode ohne das der Name danach noch mal verwendet wird, machen also keinerlei Sinn. Du dachtest vielleicht das der Name an der Stelle global ist, aber das sollte man ja sowieso nicht machen.

Werte (ausser Konstanten) betreten Funktionen oder Methoden als Argumente und verlassen sie gegebenfalls als Rückgabewerte. Es sollte nicht auf irgendwelche Variablen einfach so zugegriffen werden weil sie irgendwie in der Umgebung existieren und schon gar nicht verändert werden. Damit hat man ganz schnell ein fürchterliches Durcheinander und schwer bis gar nicht nachvollziehbaren Code der schlecht wart- und testbar ist.

`self` in einer Methode an einen neuen Wert zu binden macht keinen Sinn. Es gibt manchmal ein Missverständnis bei Anfängern die das aus einem bestimmten Grund versuchen, aber das kann ich hier nicht erkennen, das ist einfach nur völlig Banane.

Auf `BG` wird zugegriffen ohne das das vorher definiert wurde. In der `__init__()` ist das ein lokaler Name, der existiert nur dort und nur solange die Methode abgearbeitet wird. Da kann man nicht auf magische Weise von anderen Methoden die später aufgerufen werden drauf zugreifen.

`place()` sollte man nicht verwenden. Das ist nur ganz selten die richtige Wahl um Widgets anzuordnen. Hier würde man prima mit `pack()` auskommen. Einen `Frame` für den Hintergrund und darin dann die Label für die Lampen anordnen. Statt Label für die Lampen könnte man auch `Canvas`-Exemplare verwenden, oder ein `Canvas`-Exemplar für alle Lampen. Dann könnte man die als Kreise zeichnen damit es mehr nach Ampel aussieht.

Der Code erstellt immer neue und damit immer mehr `Label`-Objekte. Die alten verschwinden damit nicht aus dem Speicher und damit verbraucht das Programm immer mehr davon der nicht mehr freigegeben wird bis das Programm endet. Man erstellt die Widgets welche die Ampel ausmachen nur einmal und verändert dann deren Eigenschaften statt immer wieder neue Objekte zu erzeugen. Da wiederholt sich auch viel Quelltext zweimal und nochmal in der `__init__()` der sich nur wenig unterscheidet. So etwas vermeidet man als Programmierer weil das eine potentielle Fehlerquelle ist. Wenn man nämlich etwas an dem Code ändert muss man aufpassen das in allen Kopien zu tun und das auch noch in exakt der gleichen Art.

Auf `label` wird zugegriffen, aber das wird nicht einmal lokal in einer anderen Methode definiert. Wo dachtest Du soll das herkommen?

Falls der Aufruf/das registrieren von `count()` mittels `after()` an der Stelle eine Pause bewirken sollte: Das tut es nicht und so funktioniert das auch Grundsätzlich nicht. Man darf in GUI-Methoden nichts machen das längere Zeit blockiert, denn solange blockiert auch die GUI, das heisst auch das die Änderungen die davor vorgenommen wurden erst sichtbar werden wenn die Methode zur GUI-Hauptschleife zurückkehrt.

Die „magischen“ Methoden stehen üblicherweise vor den normalen Methoden und die `__init__()` als erstes. Da in der `__init__()` alle Attribute eingeführt werden sollten ist das die Methode die man unbedingt lesen möchte wenn man wissen möchte welche Attribute ein (nicht dokumentiertes) Objekt haben wird. Das will man dann nicht erst irgendwo am Ende der Klassendefinition suchen müssen.

Es darf in einem Programm immer nur *ein* `Tk`-Exemplar zur gleichen Zeit geben. Das ist *das* Hauptfenster. Da hängt auch der ganze Tk/Tcl-Interpreter dran. Wenn man davon mehr als ein Exemplar gleichzeitig hat, dann können komische Dinge passieren, bis hin zu harten Programmabstürzen.

Der Aufruf von `mainloop()` kehrt erst zurück wenn das Hauptfenster geschlossen wurde. Das heisst wenn Du so ein Objekt erfolgreich erstellen könntest, dann kannst Du nicht die Umschaltmethode in der Python-Shell aufrufen weil Du den Aufruf erst eingeben kannst wenn der Benutzer das Fenster wieder geschlossen hat.

Weiteres vorgehen: Erst einmal den vorhanden Quelltext wegwerfen.

Unabhängig von einer GUI die OOP-Grundlagen lernen, denn Du hast mit beidem offensichtlich Probleme und GUI-Programmierung baut auf OOP auf. Das will man als Anfänger nicht unbedingt gleichzeitig lernen müssen, sondern nacheinander.

Dann solltest Du Dir überlegen wie man die Ampel, deren Zustände, und übergänge zwischen diesen Zuständen ohne GUI modellieren könnte. Und das erst einmal machen. Wenn die Programmlogik steht, *dann* kann man eine GUI drauf setzen.

Ein Zustand kann Beispielsweise durch ein Tupel mit drei Wahrheitswerten repräsentiert werden die jeweils angeben ob die dazugehörige Lampe an oder aus ist. Dazu könnte man noch die Wartezeit zwischen dem aktuellen und dem nächsten Zustand speichern. Mit dem Wert 0 um anzuzeigen das hier nicht automatisch weitergeschaltet wird, sondern der Impuls dafür von aussen kommen muss. Die Zustandsabfolge könnte man dann in einer Liste speichern. Mit `itertools.cycle()` bekommt man einen Iterator der endlos die Zustände in der Abfolge der Liste liefert. Den kann man dann in einer GUI verwenden.

Da kommt dann eine wichtige Fähigkeit beim Programmieren ins Spiel: Probleme in kleinere Teilprobleme aufteilen, und die wieder aufteilen, solange bis sich ein Teilproblem mit ein paar Zeilen Code lösen lässt. Das dann tun, *und testen*! Und erst wenn ein Teilproblem gelöst ist, zum nächsten weitergehen oder aus kleineren Teillösungen grössere Teillösungen zusammensetzen. Auch die wieder testen und erst weitermachen wenn das funktioniert. Das macht man dann solange bis das Gesamtproblem gelöst ist.

Bei der Darstellung bietet sich beispielsweise an die Darstellung einer Ampel mit drei Lichtern in die einzelnen Lichter zu zerlegen. Also eine Klasse zu schreiben die ein Licht darstellt das eine Farbe hat und das man an und ausschalten kann. Wenn man ein funktionierendes Licht hat, dann kann man aus drei solcher Objekte die Ampel erstellen.
BlackJack

Mal ein Ansatz wie das aussehen könnte:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from collections import namedtuple
from itertools import cycle
try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

ON, OFF = True, False
Lights = namedtuple('Lights', 'red yellow green')
State = namedtuple('State', 'switch_states delay')


class LightUI(tk.Frame):

    def __init__(self, parent, color, size):
        tk.Frame.__init__(
            self,
            parent,
            width=size,
            height=size,
            relief=tk.SUNKEN,
            borderwidth=max(1, int(size * 0.05)),
        )
        self.colors = ['black', color]
        self.switch(OFF)

    def switch(self, state):
        self['background'] = self.colors[state]


class TrafficLightUI(tk.Frame):

    STATES = [
        State(Lights(ON, OFF, OFF), 0),
        State(Lights(ON, ON, OFF), 1),
        State(Lights(OFF, OFF, ON), 0),
        State(Lights(OFF, ON, OFF), 1.5),
    ]
    COLORS = ['red', 'yellow', 'green']

    def __init__(self, parent, light_size, cycle_end_callback=lambda: None):
        tk.Frame.__init__(
            self, parent, background='DarkGray', relief=tk.RAISED, borderwidth=3
        )
        self.cycle_end_callback = cycle_end_callback
        self.states = cycle(self.STATES)
        self.lights = list()
        pad_size = max(1, int(light_size * 0.1))
        for color in self.COLORS:
            light = LightUI(self, color, light_size)
            light.pack(side=tk.TOP, padx=pad_size, pady=pad_size)
            self.lights.append(light)

    def cycle(self):
        traffic_light_state = next(self.states)
        for state, light in zip(traffic_light_state.switch_states, self.lights):
            light.switch(state)
        if traffic_light_state.delay:
            self.after(int(traffic_light_state.delay * 1000), self.cycle)
        else:
            self.cycle_end_callback()


class MainFrame(tk.Frame):

    def __init__(self, parent, light_size):
        tk.Frame.__init__(self, parent)
        
        self.traffic_light = TrafficLightUI(
            self,
            light_size,
            lambda: self.cycle_button.configure(state=tk.NORMAL)
        )
        self.traffic_light.pack(side=tk.TOP)

        self.cycle_button = tk.Button(
            self, text='Schalten', command=self.do_cycle
        )
        self.cycle_button.pack(side=tk.TOP, fill=tk.X)

    def do_cycle(self):
        self.cycle_button['state'] = tk.DISABLED
        self.traffic_light.cycle()


def main():
    root = tk.Tk()
    root.title('Ampel')
    main_frame = MainFrame(root, 100)
    main_frame.pack()
    root.mainloop()


if __name__ == '__main__':
    main()
Antworten