Zugreifen auf Variablen innerhalb einer Funktion.

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.
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Hey,
folgender Code-Ausschnitt:

Code: Alles auswählen

balance_cash = 99999
def add_money():
    balance_cash += 100
add_money()
Ich habe also die Integer Variable balance_cash. Mit der Funktion add_money() möchte ich einen Wert von 100 zur Variable balance_cash hinzuzählen.
Dabei entsteht jedoch folgende Fehlermeldung:

Code: Alles auswählen

UnboundLocalError: local variable 'balance_cash' referenced before assignment
Auch folgendes funktioniert nicht:

Code: Alles auswählen

balance_cash = 99999
def add_money():
    balance_cash += 100
    return balance_cash
balance_cash = add_money()
Im Internet habe ich folgenden Beispielcode gefunden:

Code: Alles auswählen

x = 2

def f():
    print( x )

f()
Dieser funktioniert einwandfrei. Dementsprechend gehe ich davon aus, dass man innerhalb der Funktion zwar Variablen auslesen kann, allerdings nicht "bearbeiten". Lässt sich mein Vorhaben trotzdem umsetzten?
LG Spedex
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Du kannst keine globalen Variablen in einer Funktion ändern. Globale Variablen sollten eh nicht verwendet werden. Alles was eine Funktion braucht, bekommt sie über ihre Argumente und gibt das Ergebnis per return zurück:

Code: Alles auswählen

def add_money(balance):
    balance += 100
    return balance

balance_cash = 99999
balance_cash = add_money(balance_cash)
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Das was du versuchst, tut man nicht.
Funktionen erhalten alles, was sie benötigen, als Parameter und geben das Resultat mit return zurück.
Aus einer Funktion heraus auf eine Variable zuzugreifen, die sich in einem anderen Namensraum befindet, ist unsauber, nicht nachvollziehbar und kann zu seltsamen Effekten führen. Gewöhne es dir erst gar nicht an.

Code: Alles auswählen

def add_money(stock, amount):
    return stock + amount

balance_cash = 99999
balance_cash = add_money(balance_cash, 100)
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Ok. Vielen Dank
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Soweit so gut, allerdings ist mir gerade aufgefallen, dass das mit Tkinter nicht so einfach ist.
Deswegen sind jetzt wieder die Tkinter Profis gefragt.
Denn das Problem liegt dabei, dass die Funktion nicht einfach so aufgerufen wird:

Code: Alles auswählen

def add_cash(balance, menge):
    balance += menge
    return balance
add_cash(balance_cash, 100)
Die Funktion wird nämlich mit einem Button aufgerufen.
Das hat zum Beispiel folgende Form:

Code: Alles auswählen

button_add_cash.configure(command=lambda : add_cash(balance_cash, 100))
Dementsprechend die Frage: Lässt sich hier auch irgendwie der return-Value einer Variable zuweisen?
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spedex: Jede nicht-triviale GUI-Anwendung benötigt objektorientierte Programmierung. Der Kontostand ist Zustand eines Konto-Objekts, wenn man GUI und Programmlogik sauber trennt. Wenn man es nicht sauber trennt, würde sich ein `tkinter.IntVar`- oder `tkinter.DoubleVar`-Objekt anbieten.

Statt eines ``lambda``-Ausdrucks hätte ich hier übrigens `functools.partial()` verwendet.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Aha ok. Ich arbeite also mit var.set() und var.get(). Aber wie ich das jetzt nutzen soll, um den return-Value einer Variable zuzuweisen, verstehe ich leider nicht. Habe mir schon folgendes durchgelesen:
https://effbot.org/tkinterbook/variable.htm sowie https://stackoverflow.com/questions/130 ... in-command und viewtopic.php?t=30046, aber das hilft mir ehrlich gesagt nicht weiter.
Führe ich also folgendes mit meiner Variable balance_cash durch?:

Code: Alles auswählen

balance_cash = IntVar()
Wofür soll das gut sein?
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spedex: Du hast da jetzt ein Objekt das als Zustand eine ganze Zahl hat und Du kannst den Zustand auslesen (`get()`) und auf einen anderen Wert setzen (`set()`). Und das geht von überall her wenn Du auf das Objekt zugriff hast. Also im Gegensatz zum binden des Namens `balance_cash` irgendwo lokal in einer Funktion oder Methode.

Code: Alles auswählen

def main():
    ...
    cash_balance_var = tk.IntVar(value=99_999)
    ...
    add_cash_button["command"] = lambda: cash_balance_var.set(
        cash_balance_var.get() + 100
    )
    ...
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du solltest dich mit Objektorientierung auseinandersetzen. Eine Skizze:

Code: Alles auswählen

class MeineAnwendung:

    __init__(self, ...):
           self._balance = tk.IntVar()
           self._label = tk.Label(..., textvariable=self._balance)
           self._button = tk.Button(..., command=self._update_balance
    def update_balance(self):
           self._balance.set(self._balance.get() + 100)
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Ich muss sagen, mit OOP hatte ich bis jetzt noch nichts am Hut und hatte eingentlich auch vor, dass das so bleibt. Aber anscheinend komm ich nicht drum herum.
Bei mir schaut das jetzt wie folgt aus (Auschnitt):

Code: Alles auswählen

balance_cash = IntVar(value=9)
label_atm_cash_show = Label(frame_atm, text=f"{str(balance_cash)}€", font="arial 25", bg="white")
label_atm_cash_show.place(x=620, y=203) #place ist schlecht, ich weiß es
button_add_cash.configure(command=lambda : balance_cash.set(balance_cash.get() + 100))
Das sind natürlich nur Ausschnitte.
Wenn ich jetzt jedoch die Variable balance_cash anschaue, schaut die wie folgt aus: PY_VAR0 bzw. eigentlich: PY_VAR0€
Auch nachdem ich den +100 Button gedrückt habe, ändert sich nichts an balance_cash. Das bleibt weiterhin PY_VAR0. Ich kann jedoch nicht sagen, ober der Button nicht funktioniert oder ob es an was anderem liegt.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Wie schaust du denn die Variable an?
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Also ich hab sie test-weise mit print() ausgeben lassen. Aber ich hab sie auch so angeschaut, wie sie normalerweise angeschaut wird. Im Hauptfenster ist ein Button "ATM", welcher einen Frame öffnet (mit öffnen meine ich tkraise()), dort steht dann als Label die Variable. Im Hauptfenster ist ebenfalls der Button "Add Cash", welcher zum aktuellen Kontostand 100€ hinzufügt. Wenn ich den ATM-Frame öffne, wird das Label, auf dem die "balance_cash" Variable angezeigt wird, aktualisiert. Ansonsten würde dieses, selbst wenn "balance_cash" einen neuen Wert hätte, immer nur den Wert zeigen, der beim erstmaligen erstellen aller Frames vorhanden war.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Ein paar Zeichen Code sagen mehr als 1000 Worte.
__blackjack__ hat doch oben geschrieben, wie man den Wert _setzt_ und wie man den Wert _holt_. Für beides musst du eine Funktion auf dem Objekt aufrufen. set für das Setzen, get für das Holen.

Das steht auch alles in der Dokumentation.
Und wenn du dich grundsätzlich mit Sachen nicht auseinandersetzen willst - zum Beispiel Dokumentation, Objektorientierung oder wie man vernünftig eine Oberfläche baust - dann wirst du kurzfristig ein Problem mit deinen Hausaufgaben haben.
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spedex: Der Wert von `balance_cash` ist das `IntVar`-Objekt. Als Zeichenkette umgewandelt bekommt man immer den Variablennamen unter dem das im Tcl-Interpreter zur Verfügung steht. Und der Name ändert sich nicht. `tkinter` scheint da einfach Namen nach dem Muster PY_VAR<laufende nummer> zu generieren. Das ist aber letztlich nur ein Implementierungsdetail. Du willst nicht das `IntVar`-Objekt in dem Label darstellen sondern den Wert der *in* dem Objekt gespeichert ist. Wofür man den Wert dort heraus holen muss. Mit `get()`. Und wenn man das so macht, dann muss man das auch jedes mal machen wenn man den Wert aktualisiert. Automatisch geht das nur wenn man das `IntVar`-Objekt als `textvariable` an das `Label` übergibt. Dann ist das aber nur der Wert und kann keine zusätzlichen Prä- oder Suffixe enthalten. Wenn da ein € hinter stehen soll, muss man das also entweder in ein eigenes Label stecken das rechts von dem Label mit dem Zahlwert angezeigt wird, oder man muss Code schreiben der jedes mal das Label aktualisiert und den aktuellen Zahlwert + € in das Label setzt.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Ok, das hat soweit funktioniert. Vielen Dank vorerst.
Es ist nur eine Frage der Zeit, bis beim "command-Teil" des Buttons nicht eine direkte Anweisung steht (so wie eben), sondern wirklich eine Funktion. Hier zum Beispiel:

Code: Alles auswählen

def transf_geld(konto1, konto2, menge):
    konto1 -= menge
    konto2 += menge
button_atm_abheben_200.configure(command=lambda : transf_geld(balance_bank.get(), balance_cash.get(), 200))
Man bemerke. Es fehlt einerseits das return-Statement, andererseits die set Methoden.
Wenn ich die Methode auf einen return-Parameter beschränken würde, würde mit folgende Lösung einfallen:

Code: Alles auswählen

def transf_geld(konto2, menge):
    konto2 += menge
    return konto2
button_atm_abheben_200.configure(command=lambda : balance_cash.set(transf_geld(balance_cash.get(), 200)))
Das funktioniert.
Es geht also darum, dass mehr als ein Wert zurückgegeben werden muss, und diese zurückgegebenen Werte dann mit set() gleich zwei verschieden Variablen zugeordnet werden müssen.
Ich hab gelesen, dass wenn mehr als 1 Wert mit return zurückgegeben wird, das ganze als "Tuple" zurückgegeben wird. Aber wie kann ich die Einträge des Tuples dann den Variablen zuordnen.
Klar kann ich mir vorstellen, dass sowas in der Art funktioniert:

Code: Alles auswählen

var1 = tuple_xy[0]
var2 = tuple_xy[1]
oder

Code: Alles auswählen

var1.set(tuple_xy[0])
var2.set(tuple_xy[1])
Allerdings muss die Zuordnung ja innerhalb einer Zeile erfolgen, oder nicht?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und was hindert dich daran, KEIN lambda zu nehmen, so das du in aller Ruhe mehrere Statements hintereinander absetzen kannst, statt alles in genau einen Ausdruck falten zu muessen?
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Ich verstehe leider nicht ganz,was du meinst. Also soweit ich das weiß, nimmt man lambda her, um einer Funktion auch Parameter bzw. Argumente mitgeben zu können. Ohne Lambda würde das innerhalb der command-Funktion im Button zum Beispiel so aussehen.

Code: Alles auswählen

def testfunction():
    pass
button_test.configure(command=testfunction)
Man kann also keine Parameter mitgeben.
Mit lambda würde das ganze dann zum Beispiel so aussehen:

Code: Alles auswählen

def testfunction(var1):
    var1 += 10
    return var1
button_test.configure(command=lambda : testfunction(var_xy))
Deswegen bin ich davon ausgegangen, dass ich lambda benötige. Aber selbst wenn ich es nicht benötigen würde, weiß ich nicht was du mit "mehrere Statements hintereinander absetzen kannst" meinst?
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Um fixe Parameter mit zu geben, benutzt man functools.partial, wie das __blackjack__ schon in seinem ersten Beitrag geschrieben hat.
` lambda` benutzt man um EINEN Ausdruck ohne Funktionsdefinition benutzen zu können. Du hast aber nicht EINEN, sondern mehrere.
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Also mit partial würde bei mir das jetzt so aussehen:

Code: Alles auswählen

def transf_geld(konto1, konto2, menge):
    konto1 -= menge
    konto2 += menge
    # label_atm_cash_show_abh.configure(text=f"{str(balance_cash.get())}€")
    # label_atm_bank_show_abh.configure(text=f"{str(balance_bank.get())}€")
    return konto1, konto2
    
button_atm_abheben_200.configure(command=partial(transf_geld, balance_bank.get(), balance_cash.get(), 200))
Ich hab im Internet zwar einiges über partial gefunden, allerdings nirgendswo ein Beispiel wo etwas mit return zurückgegeben wird. Dementsprechend bin ich mir immer noch unsicher, wie ich jetzt die return-Werte den Variablen zuordnen soll.
Ich habe ja schätzungsweise einerseits balance_bank.set() und andererseits balance_cash.set(). Innerhalb der set-Klammern kommt denke ich mal die Funktion, die etwas zurückgibt mit return. Aber ich hab zwei set-Klammern und nur eine Funktion, die zwei Werte auf einmal zurückgibt. Wie soll ich das verwalten?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das du das .get() schon wieder in das partial reinziehst ist falsch. Du willst die Variablen doch danach als Objekt zur Verfuegung haben, nicht nur deren Werte. Und dann ist da auch kein return mehr, sondern einfach das setzen der neuen Werte.
Antworten