'Is there a way to make a function run after a specified amount of time in Python without the after method?

I am trying to create a simple program that tracks a user's clicks per second in Tkinter, but I have no idea how to make the program wait without freezing the program using the after method. The problem is that I need to log the high score after the time finishes, but using this method, the score logs before the click counter goes up. Here is my code:

from tkinter import *
import time
root = Tk()
root.geometry('600x410')
screen = Canvas(root)
h = 6 #button height
w = 12 #button width
c = 0 #counts amount of times clicked
start_btn = 0 #logs clicks of the start button
high_score = 0 #logs the highest score
time = 0

def count_hs():
    high_score = c
def remove_time():
    global time
    time -= 1
def countdown(n):
    for i in range(n):
        time = n
        root.after(1000, remove_time())
        #alternatively i tried this:
        #time.sleep(1)
        #remove_time()
        if time <= 0:
            b["text"] = "Test done."
            break

def start_test():
    global start_btn
    b["text"] = "Click to begin."
    start_btn += 1
    print("start button: " + str(start_btn))

def button_click():
    global start_btn
    global c
    c+=1
    print("click counter: " + str(c))
    #resets the amount of clicks on the large button when the start button is pressed
    if c >= 1 and start_btn >= 1:
        print("test1")
        c = 1
        start_btn = 0
        if b["text"] == "Click to begin.":
            print("test2")
            b["text"] = "Click!"
            countdown(6)
            count_hs()
            print("hs: " +str(high_score))
#primary button
b = Button(root, text=" ", font=("Arial", 40), height = h, width = w, command = lambda: button_click())
b.grid(row=0, column=0)
#start button
start = Button(root, text="Start.", command = lambda: start_test())
start.grid(row=0, column=1)

root.mainloop()


Solution 1:[1]

Give it a try

from tkinter import *

root = Tk()
root.geometry('600x410')
screen = Canvas(root)
h = 6  # button height
w = 12  # button width
c = 0  # counts amount of times clicked
start_btn = 0  # logs clicks of the start button
high_score = 0  # logs the highest score
time = 0


def count_hs():
    global high_score
    if c > high_score:
        high_score = c

    return high_score


def remove_time():
    global time
    time -= 1
    if time > 0:
        root.after(1000, remove_time)
    else:
        show_score()


def start_test():
    global start_btn
    global c
    global time
    b["text"] = "Click to begin."
    start_btn += 1
    print("start button: " + str(start_btn))

    # Reset your timer and counter
    time = 6
    c = 0


def button_click(*args):
    global start_btn
    global c
    # resets the amount of clicks on the large button when the start button is pressed
    if c == 0 and start_btn >= 1:
        start_btn = 0
        b["text"] = "Click!"
        root.after(1000, remove_time)
        print("hs: " + str(high_score))
    else:
        c += 1
    print("click counter: " + str(c))


def show_score():
    global c
    score_label.configure(text=str(c))
    high_score_label.configure(text=str(count_hs()))

    c = 0
    b['text'] = ""


# primary button
b = Button(root, text="", font=("Arial", 40), height=h, width=w, command=button_click)
b.grid(row=0, column=0, rowspan=5)

# start button
start = Button(root, text="Start.", command=lambda: start_test())
start.grid(row=0, column=1)
Label(root, text="Your score").grid(row=1, column=1)
score_label = Label(root, text="")
score_label.grid(row=2, column=1)
Label(root, text="High score").grid(row=3, column=1)
high_score_label = Label(root, text="")
high_score_label.grid(row=4, column=1)

root.mainloop()

Few changes:

  • In count_hs I assume you would update the highscore only if current score beats it.
  • You can use remove_time as a timer by making it calling itself until time <= 0, in which case you should end your game.
  • I've used the start button as a resetter, so that when it is clicked it will reset c and time.
  • On button_click you can now only bother with updating c (and change text at the beginning).
  • Finally I've added few labels to show the final results, both current and high scores.

Few suggestions to move on:

  • Instead of global variables you could create a class for the app, it should make it easier for you to exchange info and avoid subtle errors.
  • You could improve the layout, especially for the newly added labels.
  • You could make your original timer a parameter (currently, it is set within start_test).
  • Instead of importing from tkinter import *, I'd suggest you to do something like import tkinter as tk or from tkinter import ... since it increases readability and reduces the sources of errors.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ALai