Globale Variable wird in Klasse nicht erkannt!

Fragen zu Tkinter.
Antworten
michelone
User
Beiträge: 4
Registriert: Sonntag 1. April 2018, 19:27

Frohe Ostern und vielen Dank schon einmal im Voraus für eure Hilfe.
Ich bin Anfänger und habe mir folgendes überlegt als kleines Lernsript. Ein Label und ein Button in ein Fenster. Auf Knopfdruck zählt eine Zahl +1.
Ich habe ein Problem mit den Variablen. Er überträgt die Variable nicht in die Klasse, so scheint es mir zumindest.

Code: Alles auswählen

import tkinter as tk
from tkinter import Frame,Label,Button

def add():
    global number
    number = 0
    number += 1
    return number
add()

class Top:
    def __init__(self,master):
        frame01 = Frame(master)
        frame01.pack()

        self.zahl = Label(frame01,text=str(number))
        self.zahl.grid(row=0,column=0)

        self.btn01 = Button(frame01,text="ADD", command=add)
        self.btn01.grid(row=1,column=0)

root = tk.Tk()
root.title("Hochzählen")
root.minsize(width=300,height=200)
top = Top(root)
root.mainloop()
Ich würde mich auch sehr über Tipps zum "schöner scipten" freuen.
Noch einen schönen Sonntag.
Michael
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@michelone: der Text eines Labels ändert sich nicht auf magische Weise, wenn sich irgendwas, was irgendwann einmal zum ursprünglichen Text geführt hat, ändert. Du suchst IntVar.

Daneben solltest Du gleich wieder vergessen, dass es `global` überhaupt gibt, das macht nur mehr Probleme, als dass es löst. Du hast doch schon eine Klasse, in der Du die Methode `add` definieren kannst und mit einem Attribut `number` arbeiten.
michelone
User
Beiträge: 4
Registriert: Sonntag 1. April 2018, 19:27

Hallo und vielen Dank erst einmal. Das mit dem IntVar habe ich noch nicht so ganz verstanden :? . Werde mir das mal genauer angucken mit .set() und .get(). Das scheint sehr interessant zu sein.
Die Idee hinter der Form des Scriptes ist aber, die Funktionen von den Klassen zu trennen. Bei einem größeren Projekten könnte man dann diese in verschiedene *.py -files legen.
Ohne "Globale" - Variablen sehe ich da vorerst keine Möglichkeit, obwohl es diese bestimmt gibt.
Vielen Dank
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@michelone: global brauchst Du nur, um einen Zustand funktionsübergreifend zu modifizieren, ohne diesen als Parameter mitzuführen. Genau das gleiche kannst Du auch durch Klassen erreichen, aber ohne Gefahr von Seiteneffekten. Diese können schwer zu finden und später noch schwerer zu beseitigen sein. Daher hier immer wieder der Hinweis, auf global zu verzichten.

Dein global wird übrigens nicht den gewünschten Effekt haben, denn dafür müsstest Du number auch global anlegen.
michelone
User
Beiträge: 4
Registriert: Sonntag 1. April 2018, 19:27

Danke kbr. Ich werde diesen Thread mal als beendet angeben und mich erst noch weiter einlesen. Ich glaube mir fehlen noch ein paar Grundlagen.
P.S. Wie markiert man den Forenbeitrag als "Beendet"? :K Sorry, ich bin der Neue hier im Forum.
Grüsse
Michael
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hier kann man Threads nicht als "erledigt" markieren. Insofern musst du nichts weiter tun.

Und deine an sich ja sehr loeblichen Absichten angeht zu modularisieren und Logik und GUI zu entkoppeln - das geht mit einer reihe von Moeglichkeiten.Objektorientierung ist sicherlich die populaerste. Die IntVar & Co-Klassen machen das eigentlich ganz nett vor, aber deren Source ist vielleicht etwas undurchdringbar. Vom Prinzip nennt man das ein 'Observable', also ein beobachtbares Ding. Die Benachrichtigung darueber, ob es zu einer Aenderung kam, erfolgt dann ueber ein Callback/Rueckrufaktion:

Code: Alles auswählen

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

class NumberIncrementer:

    def __init__(self, start_value=0):
        self._value = start_value
        self._listeners = []

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value
        for listener in self._listeners:
            listener(self._value)

    def inc(self):
        self.value += 1


    def __iadd__(self, listener):
        self._listeners.append(listener)
        return self


def main():
    incrementer = NumberIncrementer()

    root = tk.Tk()
    frame = tk.Frame(root)
    frame.pack()

    zahl = tk.Label(frame, text=str(incrementer.value))
    zahl.pack()
    button = tk.Button(frame, text="ADD", command=incrementer.inc)
    button.pack()

    def update_label(new_value):
        zahl.configure(text=str(new_value))

    incrementer += update_label
    root.mainloop()

if __name__ == '__main__':
    main()
In echt wuerde man ein solches Programm wie von Sirius3 schon erwaehnt mit IntVar und Konsorten schreiben, weil einem da der boilerplate fuer das verdrahten automatisch abgenommen wird. Weniger Code == mehr gut. Aber als illustratives Beispiel, wie man so etwas loest, habe ich es mal ausformuliert.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Nur als Kontrast, das selbe Beispiel mit `IntVar`:

Code: Alles auswählen

from functools import partial
import tkinter as tk
 
def add(incrementer):
    incrementer.set(incrementer.get() + 1)

def main():
    root = tk.Tk()
    incrementer = tk.IntVar(root)
    frame = tk.Frame(root)
    frame.pack()
 
    tk.Label(frame, textvariable=incrementer).pack()
    tk.Button(frame, text="ADD", command=partial(add, incrementer)).pack()
    root.mainloop()
 
if __name__ == '__main__':
    main()
@michelone: sobald es aber komplexer wird, kommt man um die Verwendung von eigenen Klassen nicht herum. Jetzt hat die Funktion `add` nur ein Argument, wenn es aber mehr als 3 werden, sind das zu viele und es wird unübersichtlich und da helfen dann Klassen.
michelone
User
Beiträge: 4
Registriert: Sonntag 1. April 2018, 19:27

Danke noch mal euch beiden für die Eleganten Antworten. :D
Antworten