codeforfood
codeforfood

Reputation: 439

How to allow a function to only be called once a minute in python / tkinter?

I have a button that executes a function (in tkinter). Let's say it adds money in a video game. So when you press that button, you get 100 dollars. However, I only want my user to be able to add 100 dollars once per minute. Is there a way to only allow a function to be called once a minute? (maybe with the time module or something?)

The most i know about managing time in a function is this, I would like to do something along these lines:

def add_money():
money = money + 100
app.after(5000, add_money)

I know that this function would obviously not get what i want. Is there a way to implement it so that if you clicked the button and a minute had not passed, the program would do nothing?

Thanks in advance

Upvotes: 1

Views: 476

Answers (3)

Bryan Oakley
Bryan Oakley

Reputation: 385970

In the add_money function, disable the "add money" button, and then schedule it to be re-enabled after one minute:

def add_money():
    <do the real work here>

    add_money_button.configure(state="disabled")
    root.after(60000, lambda: add_money_button.configure(state="normal"))

If your app has multiple ways to add money, such as a menu item, a button, and an accelerator key, create a function that enables or disables all of those items, then call that function via after.

Upvotes: 1

Demian Brecht
Demian Brecht

Reputation: 21368

I'd actually likely implement this as a decorator as it sounds like something reusable. The sample below allows you to reuse guarded on any function you want, specifying the timeout (in seconds) for each function you use the decorator on:

import time
from functools import wraps

_guards = {}

def guarded(tm):
    def wrapper(fn):
        _guards[fn] = {'timeout': tm} 
        @wraps(fn)
        def inner(*args, **kwargs):
            t = time.time()
            if 'last' not in _guards[fn] or \
                t - _guards[fn]['last'] > _guards[fn]['timeout']:
                _guards[fn]['last'] = t 
                fn(*args, **kwargs)

        return inner
    return wrapper

money = 0 
@guarded(2)
def add_money():
    global money
    money += 100 
    print money

Here, add_money is only allowed after a 2 second timeout:

In [2]: add_money()
100
In [3]: add_money()
In [4]: add_money()
200
In [5]: add_money()
In [6]: add_money()
In [7]: add_money()
In [8]: add_money()
300

Yes, there are arguments for not using a global dict to track this stuff (bad when running multithreaded) and to use threading.local instead, but that's kinda outside the scope of this answer.

Upvotes: 1

chepner
chepner

Reputation: 531165

Just track time yourself, and do nothing if too little time has elapsed.

def add_money():
    curr_time = int(time.time())
    if curr_time - app.last_time > 60:
        app.last_time = curr_time
        money = money + 100

This way, the function can be called as often as the user likes, but it will only do what he wants once a minute.

Upvotes: 1

Related Questions