Reputation: 3018
I want to define an object that contains an id
, status
and countdown timer
. As soon as I instantiate this object, the countdown timer should set off immediately.
Below is the code where I create a class that has a non threaded version of the countdown timer defined in it
import time
import sys
from threading import Timer
class UserTimer:
def __init__(self, id=None, current_status=None):
self.id = id
self.current_status = current_status
self.timeout()
def timeout(self):
print("timeout started for", self.id)
timeout_limit = 150
seconds = 0
while True:
try:
if seconds == timeout_limit:
print("countdown over for", self.id)
break
time.sleep(1)
seconds += 1
except KeyboardInterrupt, e:
break
Below is how an instantiate it
params1 = {'id': "[email protected]", 'current_status': "success"}
params2 = {'id': "[email protected]", 'current_status': "success"}
user1 = UserTimer(**params1)
user2 = UserTimer(**params2)
The problem here is that when this program runs, it will instantiate the first object (user1
) and due to the function time.sleep()
, it will wait for the given duration before instantiating the second object (user2
)
So I then looked up and found python threads helpful in this situation since the threads will run independent and won't block the progression of the code.
So this is how I changed the code below
class UserTimer:
def __init__(self, id=None, current_status=None):
self.id = id
self.current_status = current_status
def timeout(self):
print("time over for", self.id)
t = Timer(150, timeout)
t.start()
params1 = {'id': "[email protected]", 'current_status': "success"}
params2 = {'id': "[email protected]", 'current_status': "success"}
user1 = UserTimer(**params1)
user2 = UserTimer(**params2)
Now both the objects get instantiated simultaneously but the problem is as soon as the given duration is over, it gives the following error
Exception in thread Thread-1:
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 1073, in run
self.function(*self.args, **self.kwargs)
TypeError: timeout() takes exactly 1 argument (0 given)
This is because it sees the keyword self
which it doesn't expect. But I need the self
since I need to dump some information about the user object. If I remove self
, then it runs fine.
Am I defining threads inside a class in a wrong manner? What I want is to be able to instantiate multiple objects that have their own countdown timers.
Additionally I should also be able to reset the countdown timer by doing something like user1.reset_timer = True
What am I doing wrong here?
Upvotes: 1
Views: 679
Reputation: 123473
I was getting a different error from your code than you were:
TypeError: timeout() missing 1 required positional argument: 'self'
The clue is the mention of self
which was being caused by the:
t = Timer(150, timeout)
t.start()
you had in the class body of UserTimer
, which means it gets executed when the class is defined (and no self
instance has been created yet).
A simple way to fix that is to make instances of the class callable by defining a __call__()
method (and calling it at the right time). Here's what I mean:
import time
import sys
from threading import Timer
class UserTimer:
def __init__(self, id=None, current_status=None, interval=5):
self.id = id
self.current_status = current_status
self.interval = interval
def timeout(self):
print("time over for", self.id)
def __call__(self):
self.timer_thread = Timer(self.interval, self.timeout)
self.timer_thread.start()
def cancel(self):
try:
self.timer_thread.cancel()
except AttributeError:
raise RuntimeError("'UserTimer' object not started.")
params1 = dict(id="[email protected]", current_status="success")
params2 = dict(id="[email protected]", current_status="success", interval=6)
user1 = UserTimer(**params1)
user1() # Start Timer.
user2 = UserTimer(**params2)
user2() # Start Timer.
I also added the cancel()
method as you requested and made the time interval an easily changed variable instead of hardcoding it into the body of the class as well.
Upvotes: 2
Reputation: 706
You should put Timer initialization in __init__()
to start it when you instantiate an object.
class UserTimer(object):
def __init__(self, id=None, current_status=None):
self.id = id
self.current_status = current_status
self.start_timer() # start timer when an object is instantiated
def timeout(self):
print("time over for", self.id)
def start_timer(self):
self.t = Timer(5, self.timeout) # changed to 5 seconds for demo
# `self.timeout` instead of `timeout`
self.t.start()
def reset_timer(self):
self.t.cancel() # cancel old timer
self.start_timer() # and start a new one
params1 = {'id': "[email protected]", 'current_status': "success"}
params2 = {'id': "[email protected]", 'current_status': "success"}
user1 = UserTimer(**params1)
time.sleep(2) # assuming that user2 is instantiated 2 seconds later
print('instantiating user2...')
user2 = UserTimer(**params2)
time.sleep(3) # reset user1's timer 3 seconds after user2 instantiation
print('reseting timer of user1...')
user1.reset_timer()
You can run the code and check the timing.
Upvotes: 1