Knappar & räknaren

Varför visar den alltid 1?

Du vet nu hur ett fönster skapas och hur en knapp kopplas till en funktion. Men det finns ett problem som nästan alla stöter på direkt: hur håller en funktion koll på vad som hänt sedan förra gången den kördes?

Kör det här i VS Code och klicka på knappen tio gånger:

import tkinter as tk

def räkna():
    count = 0
    count += 1
    etikett.config(text=str(count))

root = tk.Tk()
root.geometry("300x200")

etikett = tk.Label(root, text="0", font=("Arial", 48))
etikett.pack(pady=30)

tk.Button(root, text="Klicka!", command=räkna).pack()

root.mainloop()

Etiketten visar alltid 1. Fundera på varför innan du läser vidare.

Svaret är scope, exakt det du mötte i Anropsstacken. count = 0 skapar en ny lokal variabel varje gång funktionen anropas. Den ökas till 1, visas, och förstörs när funktionen är klar, för en lokal variabel lever bara i sin stackram. Nästa klick kör funktionen igen, och allt börjar om från noll.

Det här är inte en tkinter-egenhet, det är samma regel som fick inventoryt att glömma i Inventoryt som glömmer: något som skapas på nytt inuti varje anrop kan inte minnas något mellan anropen. Att förstå scope är att kunna svara på frågan “var lever det här värdet, och när nollställs det?”, och den frågan avgör om ditt program fungerar eller inte.

Lösningen, global

Flytta count utanför funktionen och deklarera den som global En deklaration inuti en funktion som talar om för Python att en variabel syftar på det globala scopet. Krävs när funktionen ska ändra en yttre variabel. Inte krävs för att bara läsa den. Se: Knappar & räknaren inuti:

import tkinter as tk

count = 0

def räkna():
    global count
    count += 1
    etikett.config(text=str(count))

root = tk.Tk()
root.geometry("300x200")

etikett = tk.Label(root, text="0", font=("Arial", 48))
etikett.pack(pady=30)

tk.Button(root, text="Klicka!", command=räkna).pack()

root.mainloop()

Nu bor count utanför funktionen och lever kvar mellan klicken, och global count säger att räkna() ska ändra just den, inte skapa en ny lokal. Det är Räknaren-mönstret från Mönsterkortet, fast i ett GUI.

Clickern, ett komplett program

Genren har ett namn. En clicker är ett spel som går ut på att klicka på en knapp och se ett tal växa, och Cookie Clicker från 2013 är originalet som gav upphov till hundratals efterföljare. Det är ett förvånansvärt beroendeframkallande koncept byggt på exakt det du bygger nu: ett tillstånd, en knapp som ändrar det, och en visning som hålls i takt.

import tkinter as tk

# --- Tillstånd ---
count = 0

# --- Funktioner ---
def räkna():
    global count
    count += 1
    uppdatera_visning()

def nollställ():
    global count
    count = 0
    uppdatera_visning()

def uppdatera_visning():
    etikett.config(text=f"Klick: {count}")

# --- Fönster ---
root = tk.Tk()
root.title("Klickaren")
root.geometry("300x250")

etikett = tk.Label(root, text="Klick: 0", font=("Arial", 24))
etikett.pack(pady=30)

tk.Button(root, text="Klicka!", command=räkna, padx=20, pady=10).pack(pady=5)
tk.Button(root, text="Nollställ", command=nollställ).pack()

root.mainloop()

Tre funktioner med varsitt tydligt ansvar: räkna() ändrar tillståndet, nollställ() återställer det, och uppdatera_visning() synkar gränssnittet med datan. Ingen av dem gör två saker. Det är därför både räkna() och nollställ() anropar uppdatera_visning() i stället för att var och en skriva till etiketten själv, visningen har ett hem.

Samma mönster när programmet växer

Din clicker håller ett enda tal. Ett större program håller mer, men formen är exakt densamma. Ett Brawl i fönster har flera tillståndsvariabler i stället för en:

# --- Tillstånd ---
spelare_hp = 100
fiende_hp = 80
tur = 1

# --- Funktioner ---
def attack():
    global spelare_hp, fiende_hp, tur
    # ... logik ...
    uppdatera_gui()

def uppdatera_gui():
    spelare_etikett.config(text=f"Ditt HP: {spelare_hp}")
    fiende_etikett.config(text=f"Fiendens HP: {fiende_hp}")

Mönstret är detsamma oavsett hur stort programmet blir: Tillstånd (state) Data som beskriver programmets nuvarande läge. I tkinter lagras tillstånd i globala variabler som funktionerna läser och modifierar. Se: Knappar & räknaren globalt, logik i funktioner, visning i en separat funktion. Det är inte tkinter-specifikt, det är funktionsprincipen från Anropsstacken tillämpad på ett gränssnitt som väntar på användaren.

Uppgift: En poängräknare

Skapa filen raknare.py och kör den i VS Code.

  1. Bygg en clicker som räknar klick och visar räknaren i en stor etikett. Håll tillståndet i en global variabel.

  2. Lägg till en nollställningsknapp med en egen funktion.

  3. Se till att både räkna-funktionen och nollställ-funktionen uppdaterar etiketten genom en gemensam visningsfunktion, ingen av dem ska skriva till etiketten direkt.

  4. Pusha via Source Control-panelen.

Vad händer om

Du tar bort global count från räkna-funktionen och kör? Vilket felmeddelande får du, eller vad händer med räknaren, och vad säger det om vad Python försöker göra med count?

Motivera & reflektera

Motivera varför tillståndet ligger som en global variabel utanför funktionerna, och varför visningen fått en egen funktion i stället för att skrivas i varje knappfunktion.

Nästa nivå

Belöna spelaren. Låt clickern visa ett meddelande när räknaren passerar vissa nivåer, till exempel 10, 50 och 100 klick. Etiketten kan byta text, färg eller båda.

Det låter litet men tvingar fram ett riktigt designbeslut: var hör kontrollen hemma? Lägger du den i räkna() blandar du ihop att räkna med att visa. Lägger du den i visningsfunktionen har du en funktion som läser tillståndet och avgör vad som ska stå, vilket är precis vad en Presentatör ska göra. Prova gärna båda och känn skillnaden.